A logic block is a multi-step execution pipeline — the alternative to plain entity CRUD. You define an ordered list of steps; the execution engine runs them in sequence at request time, passing data between them. Like everything else, a logic block is configuration, not deployed code.
When to use one
- You need to read or write several entities in one call.
- You need branching (
condition) or iteration (loop). - You need to call an external system (
webhook,connector) or AI (ai_call,run_crew). - You want a single, named endpoint that encapsulates a workflow.
Define vs run
You build logic blocks in the console's Logic Blocks section. Your app runs them through its nostackai endpoint.
POST /app/{orgCode}/logicblocks/{apiname}
POST /app/{orgCode}/logicblocks/{apiname}/{pathid+} The optional greedy {pathid+} suffix lets a logic block take one or more path segments. Declare each one in inputSchema.pathParams (e.g. { name: "id", required: true }) — segments map positionally from the greedy capture to input._path.<name>, and a required param missing at request time returns 400.
Streaming for long-running AI
For token-streaming or long agentic runs, invoke the logic block over the WebSocket customapi_run action instead of the HTTP route — ai_call steps stream tokens live as the run executes, so the client can render output as it's produced.
When a block runs long: async execution
A block runs synchronously by default, including blocks with ai_call and run_crew steps. If a block's steps can take longer than the HTTP gateway allows for a normal response (an open-ended AI prompt or crew, for example), turn on Run in the background for the block (runAsync: true). The whole block is then queued from its first step, and the HTTP route responds with 202 { jobId } instead of the result; poll GET /jobs/{jobId} until it completes. The setting belongs to the block, not to a step, so the response shape is known from the block's definition.
POST /app/{orgCode}/logicblocks/{apiname}
→ 202 { "jobId": "..." }
GET /app/{orgCode}/jobs/{jobId}
→ { "jobId": "...", "kind": "logicblock_run", "status": "queued" | "running" | "completed" | "failed", "result": { ... }, "errorMessage"?: "..." } Poll the jobs endpoint — the same one for every background job, including agent runs — until status is completed or failed. Once completed, result carries the exact same { result, execution } shape a fast, synchronous call would have returned — your app's response-handling code only needs one code path that checks for a jobId first.
Using the tenant's settings and secrets
A block runs on behalf of a tenant, and can read that tenant's own configuration. Settings (Tenant Management → General) are plain values: write {{params.settings.region}} in any step value, and use a dotted path for a nested one. A key that isn't set is empty.
Secrets (Tenant Management → Secrets) are different: they are never available as a variable, so they can't end up in a log or in the debug output. A webhook step takes a secret by name — {{secret:API_KEY}} in a header, the body or a query parameter (not the URL). The real value goes only into the request that is sent; the Debug Log and ?debug=true show the name. If the name isn't set for the tenant, the step stops and says which one. The webhook step can only call hosts listed under Org Settings → Outbound Allowed Domains — add the host there first (the same list connectors use).
The same tenant values are read however the block runs — from your app, from an event, in the background, over the WebSocket, or from Run Test in the console (which uses the tenant selected there). Called from your app, the tenant is always the signed-in user's: a ?tenantCode= on the request is ignored. The org's feature flags are there too, as {{params.flags.KEY}}, worked out for the user and tenant of the run. params is a reserved name; don't give a step that output name. A value you type into a step is the same for every tenant; use params for anything each tenant sets itself, like its own keys — see *One value for every tenant, or one each?* in Tenant Settings.
AI Design — generate a draft
You can describe a workflow in natural language and have AI Design generate the step definitions as a starting point. Treat the output as a first draft to review and refine — it's a build-time tool, not a runtime feature.