Report Generation Jobs¶
Overview¶
A Report Generation Job runs one or more report templates on a schedule (or on demand) and delivers the resulting PDFs to one or more destinations such as email or Google Drive. A single run can produce many PDFs by iterating over rows of a data source — for example, one statement PDF per customer.
You typically reach for a Report Generation Job when you need: - Recurring delivery of reports (daily, weekly, monthly, yearly). - One report per entity (per customer, per invoice, per region) without manually repeating the work. - A repeatable, reviewable process — optionally with a hold-for-review approval gate before deliveries fire.
Quickstart¶
This walkthrough creates a job that emails each customer their monthly statement on the 1st of every month. It exercises the three concepts new users miss most: the Repeating data source, the Schedule step, and per-row email delivery.
Prerequisites
- Your workspace admin has configured SMTP — see SMTP settings and the Email section below.
- A customers data source exists, with at least an email column.
- A customer-statement report template exists.
Walkthrough
- Open Report Generation Jobs and click New Job.
- Step 1 — Configuration. Set Job Name to
Monthly Customer Statements. Turn on Enable scheduled generation. Leave Hold deliveries for review off for now. - Step 2 — Parameters. Add a parameter
periodof type date; default to the current month start. - Step 3 — Data Sources. Pick the
customersdata source. Toggle its Repeating flag on. The job will produce one entry per row. - Step 4 — Reports. Pick
customer-statement. Bind thecustomerIdparameter to the repeating row'sid. - Step 5 — Schedule. Trigger Monthly, every
1month, on day 1, at07:00, Until Forever. Click Generate in the preview panel to confirm the next dates. - Step 6 — Delivery. Click Add Email. In To, reference the row's email column (for example,
customers.email, wherecustomersis the name you gave the repeating data source). Fill in Subject and Message. Save. - Back on the job detail page, click Run now to verify the first run produces one entry per customer and emails each one.
Job Configuration¶
Step 1 of the wizard captures the job's identity and two top-level toggles.
| Field | Purpose |
|---|---|
| Job Name | Display name shown everywhere the job appears. |
| Description | Optional free text. |
| Code | Stable, human-readable identifier. Used in the API path ({jobIdOrCode}) and in deep links — pick something short, lowercase, and stable. Changing it later breaks anything that references it by code. |
| Enable scheduled generation | When off, the job is manual-only. When on, it runs on the schedule from step 5. |
| Hold deliveries for review | When on, generated entries wait for explicit approval before any delivery fires. See Hold-for-review. |
When both options are active, reports are automatically generated but not sent. This is the "scheduled draft" pattern: the entries pile up until someone reviews and approves them.
Parameters¶
Step 2 declares the typed inputs the job uses at run time.
- Each parameter has a name, a type (string, number, date, boolean, etc.), and an optional default.
- Parameters flow into:
- report templates (passed in when each entry is generated), and
- data source queries (e.g. a
periodparameter narrows what the customers data source returns).
- On a manual Run now, you can override parameter values for that one run only — the saved defaults stay intact.
- On a scheduled run, the saved defaults are used. If a parameter has no default and no value can be derived, the job fails fast.
Data Sources¶
Step 3 attaches one or more data sources to the job. Data sources answer the question what data should the report templates be filled with at run time.
Two roles for a data source
- Shared / context. Provides values used the same way for every entry in the run — for example, a
brandingdata source that returns the workspace logo and address. - Repeating. Provides one row per entry the run should produce. Toggle the Repeating flag on the data source you want the job to iterate.
The Repeating flag in detail
- A job may mark at most one data source as Repeating.
- If no data source is marked Repeating, the run produces a single entry.
- If one is marked Repeating, the run produces one entry per row the data source returns. Each entry runs the selected reports once, with that row's columns available to:
- per-report parameter bindings (see Reports),
- per-row delivery fields such as the email To address (see Email),
- per-row delivery conditions.
- An empty repeating data source means the run produces zero entries — useful (no customers, nothing to send) but easy to mistake for a bug. See Troubleshooting.
Reports¶
Step 4 selects the report templates the job runs for each entry.
- A job can run one or more report templates per entry. They run in the order listed.
- For each report you can bind parameters either to a job-level parameter (from step 2) or to a column on the repeating row.
- Reports that fail are reported per entry — a failure on one report does not stop the others.
Multiple reports and deliveries
Email deliveries attach one PDF per report; Google Drive deliveries upload them as separate files. There is no ZIP step and no PDF concatenation — multi-report jobs ship as N separate PDF files.
If you need several reports for the same context (a cover page plus a financial detail plus a KPI summary), put them in one job. If they have different schedules or different recipients, create separate jobs.
Schedule¶
Step 5 defines when scheduled runs fire. Visible only when Enable scheduled generation is on.
Fields
| Field | Description |
|---|---|
| Start Date / Start Time | First time the schedule may fire. Earlier ticks are not back-filled. |
| Trigger | Recurrence kind: Yearly, Monthly, Weekly, Daily, Hourly, or Once. |
| Every | Interval of the trigger unit. Every 2 weeks, every 1 year, etc. |
| Until | Forever or a specific end date. |
| Allowed Days | Which weekdays a fire is allowed. A run that would land on a disallowed day is shifted or skipped per the rule below. |
| Month | When the trigger is Yearly, which month the rule anchors on. |
| At which possible occurrence of the day of the week | Picks the Nth occurrence of an allowed weekday within the month — for example, the 1st Monday. |
| Start at the end and count backwards | When on, "1st" means last, "2nd" means second-to-last, etc. — useful for "last Friday of the month". |
Worked examples
- 1st Monday of every month at 9 AM: Trigger
Monthly, every1month, Allowed Days = Monday, occurrence =1, Start at the end = off. - Last business day of every quarter at 6 PM: Trigger
Monthly, every3months, Allowed Days = M T W T F, occurrence =1, Start at the end = on. - Every weekday at 7 AM: Trigger
Daily, every1day, Allowed Days = M T W T F.
Preview
The right-hand panel previews the next several fire dates. Click Generate to refresh after changing rules. If the preview is empty, your rule selects no dates — usually because Allowed Days excludes the weekday a fixed-date rule would land on.
Deliveries¶
Step 6 attaches one or more deliveries to the job. Each delivery is independent — a single entry can fan out to multiple deliveries (for example, email the customer and archive the PDF in Google Drive).
Click Add Email or Add Google Drive to add a delivery. Each new delivery appears as a panel with its own configuration. Use the trash icon at the top right of a panel to remove it.
Email¶
Sends the entry's report PDFs to one or more recipients.
Before you start. Your workspace admin must have configured SMTP. See SMTP settings for the full setup guide; the configuration lives under workspace Connections → SMTP Settings.
Fields
| Field | Notes |
|---|---|
| From | Defaults to the workspace's SMTP address. Override only if the SMTP server permits it. |
| Reply To | Optional override. Defaults to the workspace's reply-to address. |
| To, CC, BCC | Recipients. May reference columns from the repeating row. |
| Subject | Required. Supports row-column substitution. |
| Message | Required. Rich-text editor; supports row-column substitution. |
| Delivery Condition | Optional gate evaluated per entry. See Delivery conditions. |
Per-row delivery. When the job has a repeating data source, each entry corresponds to one row, and To / CC / BCC can be templated from that row — for example, the customer's email column. This is what makes "email each customer their own statement" a single delivery instead of one job per customer.
Google Drive¶
Uploads the entry's report PDFs to a Google Drive folder.
Before you start. A workspace admin must have uploaded a Google Cloud service-account JSON key under workspace Connections → Google Cloud. The Google Drive delivery references one of those service accounts. (There is no OAuth flow.)
For a step-by-step walkthrough of the service-account setup, see Set up Google Drive jobs.
Fields
- Service account — pick one of the configured Google Cloud service accounts.
- Target folder — Drive folder ID or path the worker uploads into. The service account must have write access to it.
- Naming pattern — file name template; supports row-column substitution.
- Delivery Condition — optional gate evaluated per entry.
The delivery worker uploads each report PDF as a separate file. If the service-account credential has been revoked or has lost access to the target folder, the delivery fails and the entry's delivery status reflects that.
Delivery conditions¶
A delivery condition is an optional CxJS expression (JavaScript-like) evaluated per entry. If it returns false, that entry's delivery is skipped — generation still happens, the entry is still in the run history, but no email is sent and no file is uploaded. Empty or whitespace-only conditions are treated as "always deliver".
Available variables. The top-level keys you gave to your data sources in step 3 (e.g. customer, row). For a repeating data source, the current row is bound to that data source's name during each entry's evaluation. The same expression environment is used by email subject and message templates. Job parameters are not currently exposed to delivery conditions.
Reference a value from the store by wrapping the path in curly braces — {row.email}, {customer.tier}. Bare identifiers like row.email are not resolved against the data store.
Examples.
The expression must evaluate to a boolean. A non-boolean result, or a runtime error in the expression, fails the delivery and surfaces the error in the run history.
Multiple deliveries per job¶
Each delivery configured in step 6 fires independently for each entry. So one job can:
- Email the customer and drop the PDF into a Google Drive archive folder.
- Email different recipients with different conditions (for example, send the full report to finance, and a redacted version to the customer).
Each delivery has its own status in the run history. A failure in one delivery does not block the others.
Runs¶
A run is one execution of the job. Every run produces 1..N entries — one per row of the repeating data source, or exactly one entry if no data source is marked Repeating.
Triggering a run
- Scheduled. Fires automatically per the Schedule, if Enable scheduled generation is on.
- Manual. Click Run now on the job. You can override parameter values for that run only.
- API. See API Support.
Run history
The run history shows runs newest-first. Each row reports start time, an in-progress / finished indicator, and entry counts grouped into four buckets:
- Queued — not yet generated.
- Completed — generated and (where applicable) delivered.
- Errors — generation or delivery failed.
- Review — generated and waiting for hold-for-review approval.
These are the same buckets surfaced by the API.
Per-entry detail
Open a run to see the entry list. Each entry has its own status (one of ParametersPrepared, Queued, InProgress, PendingReview, Completed, CompletedWithErrors, GenerationFailed, DeliveryFailed, Cancelled), the report PDFs it produced (downloadable), and a per-delivery status.
Retrying and re-delivering
- Retry (per delivery) — on a single delivery row, re-attempts that delivery without regenerating the PDF. Use this when the underlying channel had a transient problem (SMTP timeout, expired credential).
- Deliver (N selected) — on the entries toolbar, queues delivery for the selected entries. Use this both for the first delivery of held entries (after review) and for re-delivering many at once.
- Generate consolidated review document — on the entries toolbar's options menu, kicks off a single PDF that stitches every entry's PDFs together for easier review. See also the
generate-review-documentendpoint.
There is no one-click "re-run the whole run" button; if you need a fresh generation, trigger a new run.
Hold-for-review¶
Hold-for-review separates generation from delivery so a human can approve outputs before they leave the system. Useful for regulated reports, quarterly statements, or any output where a typo costs more than a re-send is worth.
How it works
- On the job, turn on Hold deliveries for review (step 1 of the wizard).
- When a run fires (manual or scheduled), entries are generated normally and end in the review bucket instead of being delivered.
- A reviewer opens the run, inspects the entry PDFs, and either:
- Approves — queues the entry's deliveries (calls the same machinery as a normal scheduled run).
- Rejects — marks the entry as not to be delivered. The PDF stays in the run history for audit.
- To approve at the run level, use the API "deliver" endpoint or the in-UI bulk action.
Toggle combinations
| Enable scheduled generation | Hold deliveries for review | Behavior |
|---|---|---|
| off | off | Manual-only. Run now generates and delivers immediately. |
| on | off | Scheduled generation; deliveries fire automatically. |
| off | on | Manual-only. Run now generates; deliveries wait for approval. |
| on | on | Scheduled generation; deliveries wait for approval. (Wizard footnote: "reports are automatically generated but not sent.") |
Consolidated review document
For long runs with many entries, generate a single consolidated PDF that stitches all entry PDFs together for easier review. See the generate-review-document API endpoint.
API Support¶
The public API is the supported integration surface for jobs. Endpoints live under /api/v1/ws/{workspaceId}/jobs/....
Swagger / OpenAPI. Every endpoint described below is also documented in the interactive Swagger UI shipped with each instance. To explore it, enable Swagger in your configuration and open /swagger/index.html in a browser — you can authorize with a Personal Access Token, inspect request/response schemas, and try calls live against your own data. The full OpenAPI document is available at /swagger/v1/swagger.json and can be imported into Postman, Insomnia, or any OpenAPI client generator. See the broader API documentation and Developer Resources for official client libraries.
Authentication. Send a Personal Access Token (PAT) as a bearer token:
Issue PATs from your user avatar (top-right) under Personal Access Tokens — a modal lets you list, create (with a name and expiry), and revoke your own tokens.
Workspace scoping. Every endpoint is scoped to a workspace. {workspaceId} accepts either the numeric workspace ID or the workspace code.
Job identifiers. {jobIdOrCode} accepts either the numeric job ID or the Code value entered in step 1 of the wizard. The code is preferred for stable integration scripts because it does not change if a job is recreated.
Permissions. Each endpoint requires either Read or Write on the Jobs permission, called out below.
What the API does not cover. You cannot create, update, or delete jobs through the API; configure them in the UI. Internal admin endpoints under api/ui/... are not part of the public API and are not supported for integrators.
List jobs¶
GET /api/v1/ws/{workspaceId}/jobs
Returns every job in the workspace.
- Permission: Jobs → Read
- Response:
Job[]— array of job summaries (id, code, name, description, scheduling flags).
Start a run¶
POST /api/v1/ws/{workspaceId}/jobs/{jobIdOrCode}/runs
Triggers a new manual run. This is the most common integration entry point.
- Permission: Jobs → Write
-
Request body (
JobRunRequest, all fields optional):Field Type Purpose paramsJSON object Override job parameters for this run. dataJSON object Free-form payload available to data sources and templates. -
Response:
{ "jobRunId": <int> }— capture this and use it to poll status.
Example.
curl -X POST \
"https://your-instance.example.com/api/v1/ws/acme/jobs/monthly-statements/runs" \
-H "Authorization: Bearer $PAT" \
-H "Content-Type: application/json" \
-d '{"params":{"period":"2026-05-01"}}'
Poll run status¶
GET /api/v1/ws/{workspaceId}/jobs/{jobIdOrCode}/runs/{jobRunId}/status
Returns the current state of a run. Poll this until finished is true.
- Permission: Jobs → Read
-
Response (
JobRunStatus):Field Type Notes finishedbool True when no entries are in queued or running states. entriesint Total entries in the run. status.queuedint Pending entries. status.completedint Successfully generated entries. status.errorsint Failed entries. status.reviewint Entries waiting for hold-for-review approval.
Generate review document¶
POST /api/v1/ws/{workspaceId}/jobs/{jobIdOrCode}/runs/{jobRunId}/generate-review-document
Asynchronously generates a single consolidated PDF stitching all entry PDFs of a run, for review purposes. Only meaningful when the run was generated with hold-for-review on.
- Permission: Jobs → Write
- Response:
202 Acceptedwith{ "temporaryFileId": "<id>" }. Poll the temporary-files endpoint (out of scope of this doc; see your API reference) to download the PDF when it is ready.
Deliver entries¶
POST /api/v1/ws/{workspaceId}/jobs/{jobIdOrCode}/runs/{jobRunId}/deliver
Queues all completed entries of the run for delivery via the configured delivery channels. Use after a held run has been reviewed and approved.
- Permission: Jobs → Write
- Response:
200 OK.
How it works¶
For curious readers and support engineers — a short tour of the moving parts.
- Scheduling. A server-side
WakeUpServicecomputes the next fire time from the recurrence rule and queues a run when that time arrives. There is no external scheduler (no cron, no Hangfire-cron); the service is in-process and resilient across instance restarts. - Generation. Each run produces independent entries; entries are pushed onto a generation queue and picked up by a worker pool. One entry's failure does not stop other entries.
- Delivery. Email is sent through a throttled in-process queue so the SMTP server is not flooded. Google Drive deliveries are dispatched as Hangfire background jobs so they can retry on transient network errors.
- Persistence. Jobs, schedules, runs, entries, and delivery records are stored in the workspace database. Reviewable PDFs live in temporary file storage with a retention window.
Troubleshooting¶
| Symptom | Likely cause | What to check |
|---|---|---|
| Run produced 0 entries. | Repeating data source returned no rows. | Open the data source, run it ad-hoc with the same parameters. |
| Run produced fewer entries than expected. | Repeating data source filtered some rows out. | Confirm the parameter values used by the run; remember manual runs may use overrides. |
| Email failed: "no recipient". | The repeating row's email column is null/empty. | Add a delivery condition like {row.email} != null && {row.email} != "" to skip those rows cleanly. |
| Email failed: SMTP rejection. | SMTP misconfigured at the workspace level. | Check workspace Connections → SMTP Settings (see SMTP settings); look at the run history for the exact SMTP error string. |
| Google Drive failed: "unauthorized". | Service-account credential expired, was revoked, or lost access to the target folder. | A workspace admin re-uploads the service-account JSON under Connections → Google Cloud (or re-grants folder access); then re-deliver the affected entries. |
| Delivery never fired. | Either the delivery condition evaluated false, or the entry is in the review bucket waiting for approval. | Open the run; check the entry's per-delivery status. |
| Schedule didn't fire. | Enable scheduled generation off, or Until date passed. | Re-check step 1 and step 5 of the wizard. |
| API returns 401. | PAT missing, expired, or lacks Jobs permission. | Re-issue the PAT from your user avatar → Personal Access Tokens; confirm Read for status calls and Write for run/deliver calls. |
API returns 404 for {jobIdOrCode}. |
Code was changed in the UI after the integration was written. | Use the numeric job ID, or update the integration to the new code. |