Workflows API
Workflows let you rethink how deep-research agents are built and used. They help bridge the gap between typical agents’ inconsistent text dumps and the structured, reproducible output real product features need.
Instead of just taking a prompt, scraping some sources, and handing you a report, we operate through search graphs. These graphs collect structured knowledge tied to the original problem. The data stays consistent run-to-run, supports dynamic input parameters, and is designed to plug directly into real product experiences — not just sit in a chat window.
Right now, workflows can be created manually or based on templates. Soon, we’ll introduce a text-to-workflow builder, giving you the familiar deep-search agent feel — with all the reliability and structure our approach brings.
Quick Start
Section titled “Quick Start”Use the snippets below as drop-in examples to test the full workflow end-to-end:
import requestsimport jsonimport time
API_KEY = "your-api-key"BASE_URL = "https://api.keenable.ai"headers = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
# 1. Create workflowbody = { "name": "Product Feedback Workflow"}resp = requests.post(f"{BASE_URL}/v1/workflow_templates/1d990293-32dc-4e01-a3e2-359480134a5a", headers=headers, json=body)workflow_id = resp.json()["id"]print(f"Created workflow: {workflow_id}")
# 2. Start workflow runrun_data = { "workflow_id": workflow_id, "input": {"target_audience": "product managers"}}
resp = requests.post(f"{BASE_URL}/v1/workflows/runs", headers=headers, json=run_data)run_id = resp.json()["run_id"]print(f"Started run: {run_id}")
# 3. Poll for completionprint("Waiting for workflow to complete...")while True: resp = requests.get(f"{BASE_URL}/v1/workflows/runs/{run_id}", headers=headers) run_data = resp.json() status = run_data["status"]
if status == "succeeded": print("Workflow completed successfully!") print(json.dumps(run_data["result"], indent=2)) break elif status == "failed": print(f"Workflow failed: {run_data.get('error', 'Unknown error')}") break elif status == "canceled": print("Workflow was canceled") break else: print(f"Status: {status}...") time.sleep(5)import { setTimeout as sleep } from "timers/promises";
const API_KEY = "your-api-key";const BASE_URL = "https://api.keenable.ai";
const headers = { "X-API-Key": API_KEY, "Content-Type": "application/json",};
async function main() { // 1. Create workflow const createBody = { name: "Product Feedback Workflow", };
const createResp = await fetch( `${BASE_URL}/v1/workflow_templates/1d990293-32dc-4e01-a3e2-359480134a5a`, { method: "POST", headers, body: JSON.stringify(createBody), } );
const createJson = await createResp.json(); const workflowId = createJson.id; console.log(`Created workflow: ${workflowId}`);
// 2. Start workflow run const runData = { workflow_id: workflowId, input: { target_audience: "product managers" }, };
const runResp = await fetch(`${BASE_URL}/v1/workflows/runs`, { method: "POST", headers, body: JSON.stringify(runData), });
const runJson = await runResp.json(); const runId = runJson.run_id; console.log(`Started run: ${runId}`);
// 3. Poll for completion console.log("Waiting for workflow to complete...");
while (true) { const pollResp = await fetch(`${BASE_URL}/v1/workflows/runs/${runId}`, { headers, }); const pollJson = await pollResp.json();
const status = pollJson.status; if (status === "succeeded") { console.log("Workflow completed successfully!"); console.log(JSON.stringify(pollJson.result, null, 2)); break; } else if (status === "failed") { console.log(`Workflow failed: ${pollJson.error ?? "Unknown error"}`); break; } else if (status === "canceled") { console.log("Workflow was canceled"); break; } else { console.log(`Status: ${status}...`); await sleep(5000); } }}
main().catch(console.error);Standard Process
Section titled “Standard Process”Workflows shine when you have repeatable research tasks. Imagine generating a structured competitor analysis — and being able to run it on a schedule, always getting the same clear structure, or re-running it with different inputs (like a different company name) to get the same report format tailored to each case.
That gives you a simple, consistent flow:
- Create a workflow, either from a template or by crafting a JSON config
- Adjust it as needed — you can do it via API, but the UI makes it a lot smoother
- Run the workflow to kick off a job
- Track the run and pull the results as a structured JSON knowledge base — ready to turn into a report or query for specific insights
Workflow Config
Section titled “Workflow Config”A workflow is a graph of nodes, executed from the root down. Nodes listed in the same array run in parallel. When a node has a for_each section, all sub-nodes inside it run in parallel for each item returned by the parent node. This lets you first discover a set of objects, then automatically kick off deeper searches for each one — for example, finding a list of companies and then pulling detailed data for each.
Below is a table with all node parameters:
| Parameter | Type | Description |
|---|---|---|
type | string | Type of node, only search nodes available for now |
params | object | Optional parameters dictionary. For search there is only {"search_width": "low" / "medium" / "high"} – specifies how many queries and pages are being analyzed. Default is medium |
key_path | string | Think of this as a short property name, this will be the key for all the generated results in the final dict |
instructions | string | Natural language instructions on what to look for, any additional requirements should land here. Avoid specifying output format scheme, it is a separate param. You can specify input parameters in the instructions using curly braces like {target_audience}. Later, when running a workflow you will be able to submit specific parameter values. |
output_format | array | How results should look like, should be an array of objects (1 object is enough) |
limit | int | Limit on the number of results to generate. This directly affects cost and time of execution. Default is 10 |
for_each | array | An optional array of nodes to execute for every result generated by this node |
Below is a complete example of a workflow config:
Example JSON
{ "nodes": [ { "type": "search", "params": { "search_width": "low" }, "key_path": "products", "instructions": "Find top 5 products used by {target_audience}. Collect product names, 2-3 sentence descriptions and key competitors. Prefer recent sources. It's Oct 2025 now.", "output_format": [ { "name": "product_name", "competitors": [ "competitor" ], "description": "product_description" } ], "for_each": [ { "type": "search", "key_path": "feedback", "instructions": "Find pain points and oppositely features people actually love in this product. Keep equal balance betweem positive and negative feedback. Prefer social communities like x, threads, any other platforms with active personal discussions. Avoid official websites , official forums and commercial materials. You should collect real people's thoughts not this company employees' thoughts. Collect feedback entity type (pain_point / feature_request / positive_feedback), aggregated feedback and a list of supporting quotes. Provide at least 20 entities. Provide quotes as they appear in the source material. Don't use your own knowledge or imagination", "output_format": [ { "type": "feeback_type", "quotes": [ "quote" ], "feedback": "aggeregated feedback" } ] } ] }, { "type": "search", "key_path": "trends", "instructions": "Analyze main professional communitiies of {target_audience}. Look for major trends in how they approach work. New technologies they are trying to adapt, new major challenges they are facing. Look for both professional and personal/emotional aspects. It's Oct 2025 now. Provide at least 15 trends. For each trend provide an agrregated summary and a set of supporting quotes as they appear in the sources. Prefer platform with active social communication (x, threads) over official websited and commercial materials", "output_format": [ { "trend": "product_name", "quotes": [ "quote" ] } ] } ]}Workflow templates
Section titled “Workflow templates”Below you can find a list of existing templates, they are also available in the console. Note that these are early acceess and we will provide more fine tuned templates in the future.
| Id | Name | Input |
|---|---|---|
0b01cace-d4e3-456d-83b3-479abed39700 | Competitor Intelligence | {"company_name": "Keenable AI", "company_description": "Keenable AI provides web search API"} |
1d990293-32dc-4e01-a3e2-359480134a5a | Products Used by Target Audience | {"target_audience": "product managers"} |