/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:
Response —
{ "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:
Response —
{ "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:
Response —
{ "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:
Response —
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:
The
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
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
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.