Skip to main content
Reporter ships as one binary. RUN_MODE selects which surfaces that binary serves: api, worker, or all. The API and the worker are two roles of the same program, not two products, and they are built, versioned, and released together. That single fact shapes everything else on this page. You choose a topology at deploy time by setting an environment variable, not by assembling separate services.

Two surfaces, one binary


Run all when one process is enough, which is the usual choice for local development and small deployments. Split the roles into two deployables when report generation needs to scale on its own: rendering a large PDF costs far more than accepting the request that asked for it, and separate deployments let you size each side to its own load.

The path a report takes


1

The API accepts the request

A POST to /v1/reports names a template and its filters. Reporter takes an idempotency lock first, keyed on the X-Idempotency header when you send one and on a hash of the request body when you do not. A duplicate that is still in flight is rejected; a duplicate of a completed request replays the original report and marks the response as a replay.
2

The API validates and persists

Reporter loads the template’s field map and output format, checks the filter fields against the live schema of each data source, and writes the report with status Processing.
3

The API queues the work

It publishes a command message to the internal RabbitMQ queue and answers 201 Created with the Processing report. The caller is done here. Rendering has not started.
4

The worker renders

The worker consumes the command, skips it if the report already settled as Finished or Error, loads the template from object storage, extracts the data for each data source, renders the document, converts it to PDF when the format calls for it, and uploads the artifact.
5

The worker settles the state

It writes Finished, Partial, or Error and emits the matching event. The download operation serves the artifact once the report is Finished.

The queue between them


The API and the worker communicate over one RabbitMQ queue that carries report commands in one direction, with a dead-letter queue behind it. This queue is private plumbing between the two surfaces of the same binary. It is not an integration point, it carries no contract you should build against, and its exchange, queue, and routing key are all operator-configured. Business events are a separate channel. Reporter publishes them to their own exchange, which the operator configures and which the deployment’s reference value names reporter.events. Every emission happens after the database commit and never fails the work that produced it. High-value events go through a durable outbox rather than a direct publish, so a broker outage delays them instead of losing them.

Data extraction


The worker does not talk to your databases through hand-written queries. It runs the same extraction engine that powers Fetcher, embedded in process, with no network hop to a separate service. The engine bounds every run. The defaults allow 10 data sources per report, 50 tables per data source, 200 fields per table, four data sources extracted in parallel, and a five-minute deadline for the whole extraction. Failures are collected rather than fatal: a report whose sections partly succeed renders from what it has and settles as Partial, recording which sections failed.

Stores and artifacts


Object storage is S3-compatible: Reporter speaks the S3 protocol and any service that answers it works as a target. Template sources land under the templates/ prefix, and rendered artifacts under reports/, keyed by template and report identifier with the output format as the extension. Reporter applies no expiry of its own, so retention is a lifecycle policy you set on the bucket.

Reporter and Midaz


Midaz is a data source to Reporter, not a runtime dependency. Reporter carries no Midaz-specific code: a Midaz database is registered with the same DATASOURCE_* variables as any other PostgreSQL database, and templates address it by the configName the operator chose. Two consequences follow. Reporter starts and serves templates, deadlines, and metrics with no data source configured at all, so a Midaz outage never stops Reporter from running. And the same deployment can report over Midaz, over your own databases, and over both in one template. Point Reporter at a read replica where one exists. The extraction path only reads, but the query load of a heavy report is real, and a replica keeps it off the ledger’s write path.
See Connecting Reporter to Midaz for the variables, the configName convention, and how to confirm the source is visible.

Health and readiness


Both surfaces expose /health and /readyz before authentication, so probes reach them without a token. /health answers 503 until the startup self-probe succeeds, which lets an orchestrator restart a pod that could not reach its dependencies at boot. /readyz reports the dependencies that surface actually uses, and its response states which deployment mode the process is running in. The API surface also serves /version with the build provenance. Read the health and readiness reference for the probe contract shared across Lerian products.

Next steps


Core concepts

Templates, data sources, reports, deadlines, and the four report states.

Environment variables

Every setting that shapes a Reporter deployment.