picoflow.io Docs

Reference

HTTP API

The demo controller's three routes, the exact request and response shapes, the session header round-trip, how a non-plain content type replaces the response body, and how errors are reported.

@picoflow/core ships no HTTP layer. The contract below is AiController in the demo application — a thin adapter over FlowEngine that the tutorials and end-to-end tests assume. Copying it is cheaper than inventing a different one.

Everything here is derived from the controller’s parameter bindings, not from its Swagger decorators, which disagree in one place.

POST /ai/run

Returns HTTP 200 on success and HTTP 400 on failure.

Request

Location Name Type Required Meaning
Body flowName string yes The registered flow name
Body message string no The user turn
Body config object no Session-wide configuration, stored as flow context on the first turn
Header CHAT_SESSION_ID string no Omit to start a new session
curl -i http://localhost:8000/ai/run \
  -H 'content-type: application/json' \
  -d '{
    "flowName": "HotelFlow",
    "message": "Hi",
    "config": { "tenantId": "demo" }
  }'
The Swagger DTO names the wrong field

ApiRunBodyDto declares the field as userMessage, so the generated OpenAPI document is wrong. The controller binds @Body(K.message), and K.message is the string "message". Send message.

The header name is K.ChatSessionID, the literal string CHAT_SESSION_ID.

Response

{
  "success": true,
  "completed": false,
  "message": "Which dates should I search?",
  "session": "6870216993a135e7deb762c7",
  "contentType": "text/plain"
}
Field Meaning
success The turn ran without an unhandled error
completed The current step’s isEnd(). Stop sending the session ID once this is true
message The text to show the user
session The session ID, also returned in the CHAT_SESSION_ID response header
contentType The current step’s HttpContentType

The controller sets the CHAT_SESSION_ID response header whenever result.session is non-empty, including on failures that already have a session.

The session round-trip

The first call omits the header. PicoFlow creates a document, binds it permanently to the requested flow, and returns the new ID in both the header and session. Send that value back as the CHAT_SESSION_ID header on every subsequent turn of the same conversation.

A session ID is a cursor for one workflow, not a container for a user’s activity. Reusing it with a different flowName fails rather than switching flows.

Direct responses and content types

When the current step’s contentType is anything other than HttpContentType.Plain, the controller replaces the JSON envelope with the raw message:

if (result.contentType && result.contentType !== HttpContentType.Plain) {
  return res.type(result.contentType).send(result.message);
}
return res.send(result);

So a step that sets HttpContentType.Json returns the model’s JSON directly, with no success, completed, or session fields in the body — the session ID is still available in the response header. Client code must branch on the response Content-Type.

HttpContentType is an enum of standard MIME strings covering data formats (Json, Xml, Yaml, JsonLd, JsonApi, …), documents (Plain, Html, Markdown, Pdf, Docx, …), spreadsheets (Csv, Xlsx, …), images (Png, Jpeg, Svg, …), audio and video, and binary types (OctetStream, Zip, Gzip, Tar).

Set it at transition time with .withContentType(...) — see go() / stay() / direct().

Failures are always plain

The engine's failure envelope hard-codes contentType: HttpContentType.Plain, so an error response always arrives as the JSON envelope, never through the raw path — even for a flow whose steps normally return JSON.

GET /ai/flows

Returns FlowEngine.getFlowNames(): a JSON array of every registered flow name.

curl http://localhost:8000/ai/flows
["BasicFlow", "HotelFlow", "InvoiceFlow", "SupportFlow", "HomeInsuranceQuoteFlow", "EmployeeBenefitsFlow"]

This is the fastest check that registration succeeded after adding a flow.

POST /ai/end

Permanently deletes one session document by calling FlowEngine.deleteSession(sessionId). It takes no body; the session travels in the CHAT_SESSION_ID header.

curl -i -X POST http://localhost:8000/ai/end \
  -H 'CHAT_SESSION_ID: 6870216993a135e7deb762c7'
{ "success": true, "session": "6870216993a135e7deb762c7" }

A failure returns HTTP 400 with { "success": false, "message": "…", "session": "…" }.

Two behaviours are worth knowing. Deleting a session that does not exist succeeds — the delete is a no-op. And calling the route with no CHAT_SESSION_ID header also reports success, because deleteSession() skips an empty ID entirely.

Deletion is not completion. TerminateSessionStep finishes a workflow while keeping its document for diagnostics; this route destroys the record.

Error reporting

Every engine error is converted into the failure envelope rather than thrown, so the transport sees a resolved value:

{
  "success": false,
  "completed": true,
  "message": "FlowClass  'DemoFlow' not registered.",
  "session": "",
  "contentType": "text/plain"
}

The demo controller maps every unsuccessful result to HTTP 400. The underlying PicoFlowError codes and status codes are not carried in the response body — only error.message survives.

Condition PicoFlowError code Intended status Demo status
Stale compare-and-swap on save or delete SESSION_CONFLICT 409 400
Session ID reused with a different flow SESSION_FLOW_MISMATCH 409 400
Malformed one-flow envelope SESSION_FLOW_INVARIANT 409 400
Session backend failure SESSION_STORE_ERROR 500 400
Unregistered flow name, model or tool error, handler exception 400 400

An application that wants precise conflict semantics should catch the error classes from FlowEngine rather than relying on the envelope, and map the three 409-class codes to HTTP 409 with a Retry affordance. The engine already avoids marking the session aborted for those three, so the winning document stays intact.

For the whole failure model see Error handling and completion, and for the concurrency rules see Session stores.