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.
The five operations
Section titled “The five operations”| Operation | Request | Answer |
|---|---|---|
| Upload a document | POST /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 documents | POST /v2/documents/batch, one part files per file and optionally a part metadataList with a JSON array of metadata, one entry per file | 201, or 207 when some files failed (see below) |
| Download a document | GET /v2/documents/{documentId}?contentHash=… | 200 with the file |
| Delete a document | DELETE /v2/documents/{documentId} | 204 |
| Create a link | POST /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.
contentHashis 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 with404.storeIdisin-memory,localors3, after the store that holds the document.
Metadata
Section titled “Metadata”Everything in the metadata is optional:
| Field | Meaning |
|---|---|
contentType | The file’s media type. Without it the file part’s content type is used, else application/octet-stream. A download answers with this type |
fileName | The file name. Without it the file part’s name is used, else the document id |
expiresAt | When the document expires, as a date-time in the future. Without it the store’s default applies (below) |
processDefinitionId, processInstanceKey | The process that created the document, for your own bookkeeping |
customProperties | Any JSON object you want to keep with the document |
size | Accepted and ignored: the size in the reference is always the size of the stored file |
Other fields are refused with 400.
Document ids
Section titled “Document ids”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.
Uploading several files at once
Section titled “Uploading several files at once”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.
Choosing a store
Section titled “Choosing a store”| Store | Where documents live | Use it for |
|---|---|---|
memory | In the engine’s memory. They are gone when the engine stops | Embedded mode, tests and trying things out |
local | In a directory on the engine’s disk | Development, and a Bundled engine on one machine |
s3 | In a bucket of AWS S3 or another S3-compatible service such as MinIO | Production, 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
501with the problem typeurn: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 tomemoryorlocalrefuses to start while another node of its cell is running, and says why.
Settings
Section titled “Settings”| Variable | Default | Meaning |
|---|---|---|
CONDUCTOR_DOCUMENT_STORE | memory in Embedded, none in Bundled and Cluster | memory, 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_BYTES | 10485760 (10 MiB) | The most bytes one upload request may carry, all files together. A larger request answers 413 |
CONDUCTOR_DOCUMENT_DEFAULT_TTL | none (documents are kept) | How long a document lives when its upload names no expiresAt, for example 7d or 12h |
CONDUCTOR_DOCUMENT_SWEEP_INTERVAL | 60s | How often expired documents are deleted |
CONDUCTOR_DOCUMENT_LINK_BASE_URL | the address the link request was sent to | The public address in links of the memory and local stores, for example https://engine.example.com |
CONDUCTOR_DOCUMENT_LINK_SECRET | a new random key at every start | The 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_PREFIX | none | A folder inside the bucket, for example tinyconductor/ |
CONDUCTOR_DOCUMENT_S3_REGION | from the AWS configuration, else us-east-1 | The bucket’s region |
CONDUCTOR_DOCUMENT_S3_ENDPOINT | AWS | The address of another S3-compatible service, for example http://minio:9000 |
CONDUCTOR_DOCUMENT_S3_FORCE_PATH_STYLE | false | true 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.
A Bundled engine with a local store
Section titled “A Bundled engine with a local store”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.0Cluster nodes with MinIO
Section titled “Cluster nodes with MinIO”Add these settings to every node, and give each node the same credentials:
-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).
Expiry
Section titled “Expiry”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 with403, as is an expired link. A deleted document answers404.
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.
Tenants and permissions
Section titled “Tenants and permissions”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:
| Permission | Allows |
|---|---|
document:create | Uploading documents and creating links |
document:read | Downloading documents |
document:delete | Deleting 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.
Errors
Section titled “Errors”| Status | When |
|---|---|
400 | Malformed 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 |
404 | No such document in the caller’s tenant, an expired document, a wrong contentHash, or another store |
409 | A document with the chosen id already exists |
413 | The upload is larger than CONDUCTOR_DOCUMENT_MAX_UPLOAD_BYTES |
415 | An upload that is not multipart/form-data |
501 | No document store is configured |
Monitoring
Section titled “Monitoring”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.