/v1/tasks/:taskId/.... taskId is the workspace task UUID (or, for child workflows, parent:step:slug — the controllers split on : where they need the root id).
Endpoints group into four families:
- Lifecycle — what the UI renders and how the user responds (
/ui,/snapshots,/action,/row-action). - Events — append-only timeline used for reasoning streams and audit (
/events). - Files — upload, list, download, and provider-tagged ingest under
/files. - Data ingest — structured JSON payload submission under
/data/ingest.
Lifecycle
Combined UI state
getTask + getNextStep from api-workspace into a single UIState payload that ui-tenant can render directly. The container resolves the tenant context from the task’s tenantId, auto-refreshing on cache mismatch — so the dev/e2e wipe-and-reseed flow works without restarting the process.
Response (sketch)
Step snapshots
api-workspace’s /snapshots endpoint — what was shown + decided at each completed step. Fetched on demand when the user clicks a completed step in the sidebar.
Response — Record<string, StepSnapshot> keyed by step id.
Approve / reject
api-workspace’s /approve or /reject signal endpoint.
Body — TaskActionDto:
| Field | Type | Required | Notes |
|---|---|---|---|
type | "approved" | "rejected" | Yes | Mirrors the UI primary / failure CTAs. |
comments | string | No | Free-text comment — recorded on the audit log; mapped to reason on the workspace signal. |
{ "ok": true }.
DATA_TABLE row action
edit_cell or reject_row — forwarded to api-workspace as a user_action signal whose kind is row_action. Workflow handlers drain these via on_row_action (see UI and Temporal signals).
Body — TaskRowActionDto:
| Field | Required | Notes |
|---|---|---|
ledger | Yes | Currently "expenses" only (server-validated allow-list). |
action | Yes | "edit_cell" or "reject_row". |
rowId | Yes | Stable subledger row UUID. |
field | For edit_cell | Column key (e.g. amount_gross). |
value | For edit_cell | New scalar / null — coercion + validation in workflow / domain. |
{ "ok": true }.
Events
Append-only timeline oftask_events rows. Used for reasoning-stream rendering, audit, and the canvas “what happened next” feed.
Ingest a task event
NTRO_INTERNAL_EVENTS_KEY is set, requests must include x-ntro-events-key: <secret> — typically only ntro-worker emits to this endpoint.
Body — TaskEventDto:
| Field | Type | Required | Notes |
|---|---|---|---|
type | string (≤128) | Yes | e.g. reasoning.delta, step.completed, source.submitted. |
source | string (≤64) | Yes | Producer — typically "worker". |
sourceLabel | string (≤256) | No | Human label for the source. |
seq | int (≥0) | Yes | Monotonic per task / event stream. |
payload | object | Yes | Event payload (free-form). |
groupKey | string (≤128) | No | Optional thread / group id for streaming reasoning into a single panel. |
{ "ok": true, "event": <persisted record> }.
List events
seq order. No auth — the x-ntro-events-key requirement applies to writes only.
Response
Files
All endpoints sit under/v1/tasks/:taskId/files. Bytes live in tenant Postgres (ingest.submitted_documents.data_bytes).
List files for a task
source to scope to one runbook source slug.
Filtering by taskId keeps the per-source list from leaking duplicate file rows across wipe-and-reseed cycles.
Response
Upload a file (browser / script path)
next_step — sandboxed agents often can’t reach this URL. Same persist + signal flow as the agent path.
Body — UploadFileDto:
| Field | Required | Notes |
|---|---|---|
source | Yes | Stable source identifier — workflow activities query by this. |
data | Yes | Base64 of the file bytes. |
fileName | No | Optional override; otherwise derived. |
contentType | No | MIME type; guessed from filename if omitted. |
period | No | Period qualifier ("YYYY-MM"). |
schemaSlug | No | Extraction schema slug used downstream. |
signalTaskId / signalName | No | Workflow ID + signal name to fire after persist. From next_step.args. |
UploadResult describing the persisted document + any side effects.
Ingest a file (agent path)
api-tenant fetches the bytes server-side. This is what next_step’s submit_file action tells agents to use.
Body — IngestFileDto. Same fields as the upload DTO but with fileRef instead of data:
fileRef.provider is one of:
| Provider | Shape |
|---|---|
local | { "provider": "local", "path": "/abs/path" } |
url | { "provider": "url", "url": "https://…", "headers": {…} } |
inline_base64 | { "provider": "inline_base64", "data": "<base64>" } |
FileFetcher validates per-provider fields. Adding a new provider doesn’t require touching the DTO.
Response — UploadResult (same shape as the upload path).
Download
taskId is in the route for symmetry with the upload paths but isn’t enforced at the row level — documentRef is already a UUID.
Response — raw bytes with Content-Type from the stored row and Content-Disposition: attachment.
Data ingest
ingest.submitted_records and fires a Temporal signal per event after commit.
Body shapes
Two mutually exclusive top-level shapes — exactly one ofdata / events must be set.
Single-row (back-compat):
Field reference
| Field | Where | Required | Notes |
|---|---|---|---|
data | top-level (single) | Either data or events | Free-form JSON object. |
events[].data | per-event (bulk) | — | Same as single-row data. |
sourceRef | top-level (single) or per-event (bulk) | No | Free-form provenance JSON (e.g. {"emailId": "…"}). Persisted on the event row for audit. Ignored at top level when events is provided. |
kind | top-level | No | Discriminator shared across the batch (e.g. "expense", "refund"). |
signalTaskId | top-level | No | Workflow ID to signal once events are persisted. May be a child workflow id (parent:step:slug). |
signalName | top-level | No | Signal name on the running workflow (e.g. "data_submitted"). |
Limits
- 256 KB per event on the JSON-stringified
data; one over-cap event rejects the whole batch. - One DB transaction per batch — bulk persists atomically; one Temporal signal fires per event after commit.
Response
The shape mirrors the input:- Single body in → single
DataIngestResultout. eventsarray in →DataIngestResult[]out.
Errors
| Status | When |
|---|---|
400 Bad Request | DTO validation fails; both data and events set; over-cap event in a batch; row-action ledger not in the allow-list; non-UUID rowId. |
401 Unauthorized | POST /events without a valid x-ntro-events-key (when NTRO_INTERNAL_EVENTS_KEY is set). |
404 Not Found | Task / document not found, or task not under this tenant. |
Cross-links
UI and Temporal signals
How
/action and /row-action reach the workflow as Temporal signals.Ingest outcomes & feedback
Workflow-side handling of
submit_file / data_submitted results.