Skip to Content
We are live but in Staging 🎉
ActionsAPI Reference

Actions — API Reference

Package: dodil.data.actions.v1 · Service: ActionsService · Proto: k3_actions.proto

This is a control-plane service on k3-api. HTTP is https://api.data.dodil.io; gRPC is rpc.data.dodil.io:443. Auth is a bearer JWT in Authorization — see Conventions. Every RPC below authorizes against the selection’s (or request’s) bucket.

RPCHTTPStreaming
ListAvailableActionsPOST /actions/list
RunActionPOST /actions/runNDJSON (application/x-ndjson)
EnqueueActionPOST /actions/enqueue
EnqueueBulkActionsPOST /actions/enqueue/bulk
WatchActionRunPOST /actions/runs/watchNDJSON (application/x-ndjson)
ListActiveRunsPOST /actions/runs/active
ListRecentActionsPOST /actions/runs/list
GetActionRunPOST /actions/runs/get
SaveActionOutputPOST /actions/runs/save

ChatTurn is gone. The conversational-agent RPC and its whole message set were declared, deferred, and removed before any implementation landed — both halves returned UNIMPLEMENTED. Do not build against a chat endpoint; the action menu (ListAvailableActions + RunAction) is the live surface.

gRPC setup — grpcurl, endpoints, reflection, and field-name casing — is covered once in Conventions → Using gRPC. Request bodies use proto field names (snake_case); HTTP is camelCase both ways.

The selection model

Every dispatch carries a Selection. Its oneof shape is what K3 maps to the action’s inputs, and its pillar string ("objects" | "vectors" | "tables" | "") filters the catalog.

message Selection { string pillar = 1; // "objects" | "vectors" | "tables" | "" (agentic) oneof shape { ObjectSelection object_single = 10; // { bucket, key, content_type? } ObjectsSelection objects_multi = 11; // { bucket, keys[], content_types[]? } CollectionSelection collection_single = 12; // { bucket, collection } ChunksSelection chunks_multi = 13; // { bucket, collection, chunk_ids[] } TableSelection table_single = 14; // { bucket, table_name } NoSelection none = 15; // { bucket } — agentic } }

content_type is optional on object selections — K3 will HEAD the object server-side if you omit it. The UI carries it so it can pre-filter the menu before you click.

ListAvailableActions

Filters the Scriptum action catalog against your current selection and returns the actions that fit, with each one’s render-time form (params_form) and content-type accept-lists. Server-side it is a Scriptum ListTemplates proxy plus a K3-side filter.

POST /actions/list { "selection": { "pillar": "objects", "objectSingle": { "bucket": "kb-prod", "key": "contracts/acme.pdf" } }, "search": "" }

Response — one AvailableAction per fitting action:

{ "actions": [ { "actionId": "summarization", "name": "summarization", "description": "Multi-level document summarization with keyword extraction.", "icon": "file-text", "visibility": "primary", "invoke": "sync", "paramsForm": { "inputs": [ ], "enums": [ ], "outputs": [ ] }, "acceptedContentTypes": ["application/pdf", "text/plain", ], "acceptedExtensions": ["pdf", "txt", "md", ] } ] }

params_form is a ScriptContract — the same type the pipeline pillar exposes for template forms, so the K3-bound inputs (url, object, bucket, keys, collection, …) are stripped out; what remains is what a UI renders as the per-action form (question, target_language, llm_model, max_tokens, …).

RunAction

One-shot streaming dispatch — the right-click → pick UX. K3 resolves the selection into action inputs, injects service-account credentials, calls Scriptum, and translates thread events into ActionEvents.

HTTP transport is NDJSON: the response body is one JSON-encoded ActionEvent per line (Content-Type: application/x-ndjson), no envelope and no trailing comma. Consume it with fetch() + a ReadableStream + line-buffered JSON.parse. SSE is deliberately not used — EventSource cannot carry the bearer token. A mid-stream error is folded into a final NDJSON line, so the terminator is always structured.

POST /actions/run // → application/x-ndjson { "selection": { "pillar": "objects", "objectSingle": { "bucket": "kb-prod", "key": "contracts/acme.pdf" } }, "actionId": "summarization", "params": { "style": "bullets" } }

Response body, one event per line:

{"started":{"actionId":"summarization","threadId":"th_…","runId":"run_…","startedAtMs":"1724930000000"}} {"progress":{"stepPath":"1.0","stepName":"map","value":{ }}} {"completed":{"output":{"summary":"…","keywords":["…"]},"durationMs":"8200"}}

A failure terminates the stream with a failed line instead:

{"failed":{"error":"…","failedStepPath":"1.0","failedStepName":"map"}}

For sync actions the stream is startedcompleted with no progress yields. output is the action’s final output_json, rendered against the template’s declared outputs (the same ScriptContract).

EnqueueAction

The queued twin of RunAction: same request shape, but instead of streaming it publishes the dispatch onto the K3_ACTIONS work queue and returns the allocated run_id at once. K3-side adaptation errors (missing required field, bad selection, Scriptum down) surface synchronously here; dispatch-time errors land in the manifest as FAILED and surface via ListRecentActions.

POST /actions/enqueue { "selection": { "pillar": "objects", "objectSingle": { "bucket": "kb-prod", "key": "contracts/acme.pdf" } }, "actionId": "translation", "params": { "target_language": "fr" }, "batchId": "" }
{ "runId": "run_…", "reverseTs": "…", "batchId": "" }

EnqueueBulkActions

Fan one action across N selections in a single round-trip — the “100 files × 1 translation” case becomes one request returning 100 run_ids. Partial failure is per-selection: run_ids[i] and errors[i] are aligned arrays, so a bad selection fails only its own slot. A top-level error means nothing was enqueued.

POST /actions/enqueue/bulk { "selections": [ { "pillar": "objects", "objectSingle": { "bucket": "kb-prod", "key": "docs/a.pdf" } }, { "pillar": "objects", "objectSingle": { "bucket": "kb-prod", "key": "docs/b.pdf" } } ], "actionId": "translation", "params": { "target_language": "fr" } }
{ "batchId": "…", "runIds": ["run_a", "run_b"], "errors": ["", ""] }

WatchActionRun

Stream the live ActionEvents for a single queued (or streaming) run — same NDJSON encoding as RunAction, so one parser serves both. Closing the stream client-side does not cancel the dispatch; the run is queue-owned. If the run has already terminated, the stream falls back to the manifest.

POST /actions/runs/watch // → application/x-ndjson { "runId": "run_…", "bucket": "kb-prod" }

ListActiveRuns

The in-flight queue snapshot for a bucket — a refresh-proof, cross-tab view of what is running right now. Pairs with ListRecentActions for terminal history.

POST /actions/runs/active { "bucket": "kb-prod" }
{ "runs": [ { "runId": "run_…", "actionId": "translation", "selectionLabel": "a.pdf", "batchId": "…", "phase": "ACTIVE_RUN_PHASE_RUNNING", "enqueuedAtMs": "…", "startedAtMs": "…", "threadId": "th_…" } ] }

Run history — ListRecentActions, GetActionRun, SaveActionOutput

Every terminal dispatch writes a manifest at _actions/runs/{reverse_ts}_{run_id}/manifest.json. Listing is one S3 LIST (reverse-timestamp order → newest first) plus N parallel manifest GETs — no sidecar index. status / action_id filters are applied after the fetch.

POST /actions/runs/list { "bucket": "kb-prod", "limit": 20 }
POST /actions/runs/get { "bucket": "kb-prod", "runId": "run_…", "inlineSibling": false }
POST /actions/runs/save { "bucket": "kb-prod", "runId": "run_…", "s3": { "targetBucket": "kb-prod", "targetKey": "reports/acme-summary.md" } }

An ActionRunManifest records status (ACTION_RUN_STATUS_{RUNNING,COMPLETED,FAILED,CANCELLED}), timing, the selection and params it ran with, an output_kind renderer hint (JSON / TEXT / MARKDOWN / TABLE_ROWS / FILE / CHUNKS / NONE), and where the output lives:

  • inline — small payloads (output_inline, ≤ ~16 KB) sit in the manifest itself.
  • sibling file — medium payloads at _actions/runs/{reverse_ts}_{run_id}/output.{ext}.
  • self-routed — templates that write to a warehouse table (WarehouseRowsRef) or an S3 key (S3ObjectRef) at run time, or vector chunks (VectorChunksRef) for chunks-kind actions.

Temp runs under _actions/runs/ carry a TTL via S3 lifecycle. SaveActionOutput copies the output to a permanent S3 key or appends its rows to a warehouse table (merge uses the table’s merge keys) and stamps the manifest’s promoted field — object content is unchanged, and the call is idempotent on (run_id, target_key).

See also