Skip to content

Documents

Processes often work with files: an invoice a customer uploads in a form, a PDF an HTTP call returns, a signed contract a connector passes on. The engine stores such files for you. An app uploads a file and gets back a small document reference. The reference goes into process variables like any other value, and whoever needs the file later downloads it with the reference, or hands out a link that works for a limited time.

The documents API works in all three modes (Embedded, Bundled and Cluster). It is served by the same REST API as everything else, so the official clients and the connector runtime use it without changes.

OperationRequestAnswer
Upload a documentPOST /v2/documents, multipart/form-data with the file in the part file and optional metadata (JSON) in the part metadata. Query: documentId (optional)201 with the reference
Upload several documentsPOST /v2/documents/batch, one part files per file and optionally a part metadataList with a JSON array of metadata, one entry per file201, or 207 when some files failed (see below)
Download a documentGET /v2/documents/{documentId}?contentHash=…200 with the file
Delete a documentDELETE /v2/documents/{documentId}204
Create a linkPOST /v2/documents/{documentId}/links?contentHash=… with {"timeToLive": 600000} (milliseconds, optional)201 with {"url": …, "expiresAt": …}

Every operation also takes an optional storeId query parameter: the store named in the reference. It must be the engine’s own store.

A reference looks like this:

{
"camunda.document.type": "camunda",
"storeId": "in-memory",
"documentId": "7b1e0c1e-5f0d-4b61-9d8f-6a0c2f8b1f3a",
"contentHash": "3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b",
"metadata": {
"contentType": "application/pdf",
"fileName": "invoice.pdf",
"size": 48213,
"expiresAt": null,
"processDefinitionId": "order",
"processInstanceKey": "2251799813690746",
"customProperties": {"department": "billing"}
}
}
  • The first property marks the value as a document reference; compatible clients and connectors recognize references by it. Keep the reference exactly as you received it.
  • contentHash is the SHA-256 of the file. Pass it back when you download the file or create a link; a download with a different hash is refused with 404.
  • storeId is in-memory, local or s3, after the store that holds the document.

Everything in the metadata is optional:

FieldMeaning
contentTypeThe file’s media type. Without it the file part’s content type is used, else application/octet-stream. A download answers with this type
fileNameThe file name. Without it the file part’s name is used, else the document id
expiresAtWhen the document expires, as a date-time in the future. Without it the store’s default applies (below)
processDefinitionId, processInstanceKeyThe process that created the document, for your own bookkeeping
customPropertiesAny JSON object you want to keep with the document
sizeAccepted and ignored: the size in the reference is always the size of the stored file

Other fields are refused with 400.

Without documentId the engine generates a new id. When you choose the id yourself it may be 1 to 256 characters of ASCII letters, digits, -, _ and .. The id batch is reserved. An id that already exists answers 409.

The batch upload stores each file on its own. If every file is stored, the answer is 201. If some fail, it is 207, and the answer lists the stored references in createdDocuments and one entry per failed file in failedDocuments, with its file name, status, title and reason. You can retry just the failed files.

Every file of a batch needs a file name, from its part or from its metadata. metadataList must have exactly as many entries as there are files.

StoreWhere documents liveUse it for
memoryIn the engine’s memory. They are gone when the engine stopsEmbedded mode, tests and trying things out
localIn a directory on the engine’s diskDevelopment, and a Bundled engine on one machine
s3In a bucket of AWS S3 or another S3-compatible service such as MinIOProduction, and any Cluster of more than one node
  • Embedded uses the memory store unless you choose another. Resetting the Embedded engine deletes every document of the memory or local store along with everything else; documents in an S3 bucket are left as they are.
  • Bundled and Cluster have no store until you choose one. Until then every document operation answers 501 with the problem type urn:bpm:error:document-store-not-configured.
  • A Cluster of more than one node needs s3. Every node, and the connector runtime, must see the same documents. A node set to memory or local refuses to start while another node of its cell is running, and says why.
VariableDefaultMeaning
CONDUCTOR_DOCUMENT_STOREmemory in Embedded, none in Bundled and Clustermemory, local or s3
CONDUCTOR_DOCUMENT_LOCAL_PATH(required for local)The directory of the local store. It is created when missing. Only one engine may use it
CONDUCTOR_DOCUMENT_MAX_UPLOAD_BYTES10485760 (10 MiB)The most bytes one upload request may carry, all files together. A larger request answers 413
CONDUCTOR_DOCUMENT_DEFAULT_TTLnone (documents are kept)How long a document lives when its upload names no expiresAt, for example 7d or 12h
CONDUCTOR_DOCUMENT_SWEEP_INTERVAL60sHow often expired documents are deleted
CONDUCTOR_DOCUMENT_LINK_BASE_URLthe address the link request was sent toThe public address in links of the memory and local stores, for example https://engine.example.com
CONDUCTOR_DOCUMENT_LINK_SECRETa new random key at every startThe key that signs links of the memory and local stores: at least 32 bytes. Set it to keep links working across a restart
CONDUCTOR_DOCUMENT_S3_BUCKET(required for s3)The bucket. It must exist; the engine checks it at startup
CONDUCTOR_DOCUMENT_S3_PREFIXnoneA folder inside the bucket, for example tinyconductor/
CONDUCTOR_DOCUMENT_S3_REGIONfrom the AWS configuration, else us-east-1The bucket’s region
CONDUCTOR_DOCUMENT_S3_ENDPOINTAWSThe address of another S3-compatible service, for example http://minio:9000
CONDUCTOR_DOCUMENT_S3_FORCE_PATH_STYLEfalsetrue for MinIO and other services that expect the bucket name in the path

Durations take the units ms, s, m, h and d. The engine reads these settings once, at startup.

The S3 store finds its credentials the way AWS tools do: from AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, from a shared credentials file, or from the role of the machine, container or Kubernetes service account it runs under. It needs permission to read, write, delete and list objects under its prefix, and to check the bucket.

Terminal window
docker run -d --name tinyconductor -p 127.0.0.1:8080:8080 \
-v tinyconductor-pgdata:/var/lib/postgresql/data \
-v tinyconductor-documents:/var/lib/tinyconductor/documents \
-e CONDUCTOR_DOCUMENT_STORE=local \
-e CONDUCTOR_DOCUMENT_LOCAL_PATH=/var/lib/tinyconductor/documents \
registry.tinyfactory.ai/tinyblox/tinyconductor-bundled:0.1.0

Add these settings to every node, and give each node the same credentials:

Terminal window
-e CONDUCTOR_DOCUMENT_STORE=s3 \
-e CONDUCTOR_DOCUMENT_S3_BUCKET=tinyconductor-documents \
-e CONDUCTOR_DOCUMENT_S3_ENDPOINT=http://minio:9000 \
-e CONDUCTOR_DOCUMENT_S3_FORCE_PATH_STYLE=true \
-e AWS_ACCESS_KEY_ID=<access key> \
-e AWS_SECRET_ACCESS_KEY=<secret key>

On Kubernetes the chart’s documents.* values set all of this, with the credentials taken from a Secret (Kubernetes).

A document with an expiresAt in the past is gone as far as the API is concerned: downloads, links and deletes answer 404 at once. The engine then deletes it for good at its next sweep. A document without an expiry is kept until you delete it.

A link lets someone download one document without a credential, until the link expires. The default lifetime is one hour, and the longest is seven days.

  • With the S3 store the link is a pre-signed address of the bucket: the download goes straight to the storage service.
  • With the memory and local stores the link points to the engine, under /document-links/. It names the tenant, the document and the expiry and is signed with the engine’s link key. Any change to the link is refused with 403, as is an expired link. A deleted document answers 404.

Anyone who has a link can download the document until it expires, so hand links only to those who should see the file, and keep lifetimes short.

A document belongs to the tenant that uploaded it. Every operation acts in the tenant of the request, so another tenant’s download, link and delete answer 404, exactly as for a document that does not exist, even for a caller who holds the document permissions.

Three permissions control the API:

PermissionAllows
document:createUploading documents and creating links
document:readDownloading documents
document:deleteDeleting documents

They are granted like every other permission, to a role, a group, a user or a client, on the resource type DOCUMENT with the id * (Identity and access). The built-in admin role has them. A connector runtime that stores or reads documents needs document:create and document:read in addition to its job permissions.

StatusWhen
400Malformed metadata, an invalid document id or time to live, another store in an upload, or a batch whose metadata list does not match its files
404No such document in the caller’s tenant, an expired document, a wrong contentHash, or another store
409A document with the chosen id already exists
413The upload is larger than CONDUCTOR_DOCUMENT_MAX_UPLOAD_BYTES
415An upload that is not multipart/form-data
501No document store is configured

Failures of the store (a full disk, an unreachable bucket, missing permissions) answer 500 without details and are logged with their cause. They are counted in the metric tinyconductor_documents_store_errors_total, with the labels operation (upload, download, delete, link or sweep) and store. See Observability.