Home / Docs / Packages / @bpmnkit/api

@bpmnkit/api

Fully-typed Camunda 8 REST API client with OAuth2, caching, and retry.

Core Semver at 1.0: nothing breaks without a major release.

Overview

@bpmnkit/api is a complete TypeScript client for the Camunda 8 Orchestration Cluster REST API:

  • 180 typed methods across 30+ resource classes
  • Auth: OAuth2, Bearer token, Basic, and no-auth
  • LRU+TTL cache for read-heavy operations
  • Exponential backoff with configurable retry
  • TypedEventEmitter for observability hooks
  • Zero transitive runtime dependencies

Installation

pnpm add @bpmnkit/api

Client Configuration

baseUrl is the cluster’s Orchestration Cluster REST address, ending in /v2. On Camunda SaaS that is ZEEBE_REST_ADDRESS from a cluster’s API client credentials file.

import { CamundaClient } from "@bpmnkit/api";

const client = new CamundaClient({
  baseUrl: `${process.env.ZEEBE_REST_ADDRESS}/v2`,
  auth: {
    type: "oauth2",
    clientId: process.env.ZEEBE_CLIENT_ID ?? "",
    clientSecret: process.env.ZEEBE_CLIENT_SECRET ?? "",
    tokenUrl: process.env.ZEEBE_AUTHORIZATION_SERVER_URL ?? "",
    audience: "zeebe.camunda.io",
  },
  // Optional:
  cache: {
    enabled: true,    // cache eventually-consistent reads (default: false)
    maxSize: 500,     // entries (default: 500)
    ttl: 30_000,      // ms (default: 30_000)
  },
  retry: {
    maxAttempts: 3,   // default: 3
    initialDelay: 100,
    maxDelay: 5_000,
  },
});

With no arguments, new CamundaClient() reads CAMUNDA_BASE_URL and the CAMUNDA_AUTH_* variables, or a config file named by CAMUNDA_CONFIG_FILE.

Resource Namespaces

Methods are grouped by resource, one property per tag of the API spec. The most used:

NamespaceMethods (selection)
client.resourcecreateDeployment, getResource, deleteResource
client.processInstancecreateProcessInstance, searchProcessInstances, getProcessInstance, cancelProcessInstance
client.processDefinitionsearchProcessDefinitions, getProcessDefinition, getProcessDefinitionXML
client.jobactivateJobs, completeJob, failJob, throwJobError
client.incidentsearchIncidents, getIncident, resolveIncident
client.variablesearchVariables, getVariable
client.messagepublishMessage, correlateMessage
client.signalbroadcastSignal
client.decisionDefinitionevaluateDecision, searchDecisionDefinitions
client.userTasksearchUserTasks, getUserTask, assignUserTask, completeUserTask

Users, groups, roles, tenants, authorizations, documents, batch operations and the rest have their own namespaces (client.user, client.group, …).

Process Operations

// Deploy: a multipart upload, one `resources` part per file
const form = new FormData();
form.append("resources", new Blob([bpmnXml]), "my-flow.bpmn");
await client.resource.createDeployment(form);

// Start instance
const instance = await client.processInstance.createProcessInstance({
  processDefinitionId: "my-flow",
  variables: { customerId: "cust-001" },
});

// List active instances
const { items } = await client.processInstance.searchProcessInstances({
  filter: { processDefinitionId: "my-flow", state: "ACTIVE" },
});

// Cancel instance
await client.processInstance.cancelProcessInstance(instance.processInstanceKey);

Job Workers

For a long-running worker, use @bpmnkit/worker-client. To handle a batch of jobs yourself:

const { jobs } = await client.job.activateJobs({
  type: "send-email",
  maxJobsToActivate: 10,
  timeout: 60_000,          // job lock duration in ms
  worker: "email-worker-1",
});

for (const job of jobs) {
  try {
    await sendEmail(job.variables);
    await client.job.completeJob(job.jobKey, { variables: { emailSent: true } });
  } catch (err) {
    await client.job.failJob(job.jobKey, {
      errorMessage: String(err),
      retries: job.retries - 1,
    });
  }
}

Incident Resolution

// Find all incidents for a process instance
const { items: incidents } = await client.incident.searchIncidents({
  filter: { processInstanceKey: instance.processInstanceKey },
});

// Fix the problem in your code, then resolve
for (const { incidentKey } of incidents) {
  if (incidentKey) await client.incident.resolveIncident(incidentKey);
}

Message Correlation

await client.message.publishMessage({
  name: "payment-confirmed",
  correlationKey: "ord-456",
  variables: {
    paymentMethod: "card",
    confirmedAt: new Date().toISOString(),
  },
  timeToLive: 60_000,   // ms — how long to wait for a matching instance
});

Observability Events

client.on("request",  (e) => logger.debug(e.method, e.url));
client.on("response", (e) => metrics.histogram("api.latency", e.durationMs));
client.on("error",    (e) => logger.error(e.url, e.error.message));
client.on("retry",    (e) => logger.warn(`Retrying ${e.url} (attempt ${e.attempt})`));

The other events are rawResponse, tokenRefresh, cacheHit and cacheMiss.