Skip to content

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.

Use the snippets below as drop-in examples to test the full workflow end-to-end:

workflow-quickstart.py
import requests
import json
import time
API_KEY = "your-api-key"
BASE_URL = "https://api.keenable.ai"
headers = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
# 1. Create workflow
body = {
"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 run
run_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 completion
print("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)

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

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:

ParameterTypeDescription
typestringType of node, only search nodes available for now
paramsobjectOptional 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_pathstringThink of this as a short property name, this will be the key for all the generated results in the final dict
instructionsstringNatural 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_formatarrayHow results should look like, should be an array of objects (1 object is enough)
limitintLimit on the number of results to generate. This directly affects cost and time of execution. Default is 10
for_eacharrayAn 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"
]
}
]
}
]
}

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.

IdNameInput
0b01cace-d4e3-456d-83b3-479abed39700Competitor Intelligence{"company_name": "Keenable AI", "company_description": "Keenable AI provides web search API"}
1d990293-32dc-4e01-a3e2-359480134a5aProducts Used by Target Audience{"target_audience": "product managers"}
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...