Workflow API¶
Integrate Kuration’s project workflow using the enterprise REST API (API v1.0 in the product UI). The same surface is documented in the app under /settings/integration?tab=api.
Base URL¶
https://api.kurationai.com/api/enterprise
All paths below are relative to this base (e.g. GET /projects → https://api.kurationai.com/api/enterprise/projects).
Authentication¶
Send your API key on every request:
kur-api-key: YOUR_API_KEY
Use common JSON headers as needed:
Accept: application/json
Content-Type: application/json
Generate or rotate the key in Settings → Integrations → API (/settings/integration?tab=api).
Try it from this page¶
Each endpoint below includes a Try this request panel. Paste kur-api-key once — it is synced across panels and saved in sessionStorage for this tab only (never sent to the docs server). Requests go only to api.kurationai.com.
POST requests change live data
POST /projects creates a real project and may start builder work (quota/cost). POST .../rows inserts real rows and may trigger tools. Prefer low max_results and test workspaces while experimenting.
If a panel fails (CORS)
In-browser requests only work when this site’s origin is allowed by the API (local mkdocs serve, *.github.io, docs.kurationai.com, etc.). If you see a network error, use cURL or run JavaScript from your own app instead.
Plan gating
If your workspace does not have API access, endpoints that require a key will not be usable until your plan includes API access.
How it works¶
A typical automation flow:
- Submit company data —
POST .../projects/{project_id}/rowswith a JSON body keyed by column names (see Save company data). - Receive a row ID — The response includes
row_id; Kuration may run tool columns asynchronously for that row. - Fetch results — Poll
GET .../projects/{project_id}/rows/{row_id}(or list rows with pagination) to read enriched column values.
You may also create projects from a builder with POST /projects when you supply a builder_id and form_data (see below).
Projects¶
Create project¶
POST /projects
Creates a project from a strict builder payload and starts builder execution asynchronously shortly after creation.
Request body
| Field | Type | Description |
|---|---|---|
builder_id |
string | Builder identifier (e.g. google_map_builder). |
form_data |
object | Fully filled builder form (same shape as the product submission). |
Example
import requests
import json
url = "https://api.kurationai.com/api/enterprise/projects"
headers = {
"accept": "application/json",
"Content-Type": "application/json",
"kur-api-key": "YOUR_API_KEY",
}
data = {
"builder_id": "google_map_builder",
"form_data": {
"query": "seed stage fintech companies in sf",
"max_results": 50,
},
}
response = requests.post(url, headers=headers, json=data)
print(response.status_code)
print(json.dumps(response.json(), indent=2))
curl -sS -X POST "https://api.kurationai.com/api/enterprise/projects" \
-H "accept: application/json" \
-H "Content-Type: application/json" \
-H "kur-api-key: YOUR_API_KEY" \
-d '{"builder_id":"google_map_builder","form_data":{"query":"seed stage fintech companies in sf","max_results":50}}'
const url = "https://api.kurationai.com/api/enterprise/projects";
const response = await fetch(url, {
method: "POST",
headers: {
accept: "application/json",
"Content-Type": "application/json",
"kur-api-key": "YOUR_API_KEY",
},
body: JSON.stringify({
builder_id: "google_map_builder",
form_data: {
query: "seed stage fintech companies in sf",
max_results: 50,
},
}),
});
console.log(response.status, await response.json());
Response (201/200 on success — follow status_code from your client)
{
"project_id": "9d6f0b7b-..."
}
Errors
| HTTP | Meaning |
|---|---|
400 |
Invalid builder_id / form_data (validation). |
500 |
Unexpected failure creating or kicking off the build. |
Try this request
List projects¶
GET /projects
Returns projects your API user can access, ordered by updatedAt descending. The response wraps a projects array plus pagination metadata (not a bare JSON array).
Query parameters
| Param | Type | Default | Constraints | Description |
|---|---|---|---|---|
limit |
int | 50 |
1–200 | Page size. |
offset |
int | 0 |
≥ 0 | Rows to skip (ignored if page is set). |
page |
int | (unset) | ≥ 1 | 1-based page; when set, offset is computed as (page - 1) * limit. |
name_search |
string | (unset) | — | Case-insensitive substring filter on project name. |
total is the full count after the name filter (not only the current page).
Minimal example (matches the in-app snippet)
import requests
import json
url = "https://api.kurationai.com/api/enterprise/projects"
headers = {
"accept": "application/json",
"kur-api-key": "YOUR_API_KEY",
}
response = requests.get(url, headers=headers)
print(response.status_code)
print(json.dumps(response.json(), indent=2))
curl -sS "https://api.kurationai.com/api/enterprise/projects" \
-H "accept: application/json" \
-H "kur-api-key: YOUR_API_KEY"
const url = "https://api.kurationai.com/api/enterprise/projects";
const response = await fetch(url, {
headers: {
accept: "application/json",
"kur-api-key": "YOUR_API_KEY",
},
});
console.log(response.status, await response.json());
Paginated example
import requests
url = "https://api.kurationai.com/api/enterprise/projects"
headers = {
"accept": "application/json",
"kur-api-key": "YOUR_API_KEY",
}
params = {"page": 2, "limit": 25, "name_search": "Q4"}
response = requests.get(url, headers=headers, params=params)
print(response.json())
curl -sS -G "https://api.kurationai.com/api/enterprise/projects" \
-H "accept: application/json" \
-H "kur-api-key: YOUR_API_KEY" \
--data-urlencode "page=2" \
--data-urlencode "limit=25" \
--data-urlencode "name_search=Q4"
const base = "https://api.kurationai.com/api/enterprise/projects";
const params = new URLSearchParams({
page: "2",
limit: "25",
name_search: "Q4",
});
const response = await fetch(`${base}?${params}`, {
headers: {
accept: "application/json",
"kur-api-key": "YOUR_API_KEY",
},
});
console.log(response.status, await response.json());
Try this request (paginated query)
Response shape (illustrative)
{
"projects": [
{
"id": "98e79cf8-87c9-49b0-9899-58edb6f3c98b",
"name": "Tech Companies Q4",
"creator_id": "user_abc123",
"company_count": 150,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-20T14:45:00Z"
}
],
"total": 42,
"limit": 50,
"offset": 0,
"page": 1
}
Difference from shorthand examples
Some UI examples display only a JSON array of projects; the live API returns the object above including projects, total, limit, offset, and page.
Get project details¶
GET /projects/{project_id}
Returns column schema (for mapping inputs), first_company_data (sample row), row_count, and builder_status. The backend may also include lead_gen_execution_log for certain project types.
Example
import requests
import json
project_id = "YOUR_PROJECT_ID"
url = f"https://api.kurationai.com/api/enterprise/projects/{project_id}"
headers = {
"accept": "application/json",
"kur-api-key": "YOUR_API_KEY",
}
response = requests.get(url, headers=headers)
print(response.status_code)
print(json.dumps(response.json(), indent=2))
curl -sS "https://api.kurationai.com/api/enterprise/projects/YOUR_PROJECT_ID" \
-H "accept: application/json" \
-H "kur-api-key: YOUR_API_KEY"
const projectId = "YOUR_PROJECT_ID";
const url = `https://api.kurationai.com/api/enterprise/projects/${projectId}`;
const response = await fetch(url, {
headers: {
accept: "application/json",
"kur-api-key": "YOUR_API_KEY",
},
});
console.log(response.status, await response.json());
Try this request
Example response (shape)
{
"columns": [
{
"col_id": "0674c9a3-8d34-4117-94ba-2e4e244e48c9",
"name": "company_name",
"required": true
},
{
"col_id": "4905e83c-bbb5-462f-9917-73d2ed14727c",
"name": "website",
"required": true
},
{
"col_id": "466527f1-ef79-4cd6-bf03-c878681da41b",
"name": "industry",
"required": false
}
],
"first_company_data": {
"company_name": "Acme Corporation",
"website": "https://acme.com",
"industry": "Technology"
},
"row_count": 1,
"builder_status": { "type": "completed", "error_message": null }
}
Errors
| HTTP | Meaning |
|---|---|
404 |
Project missing or no access. |
Rows¶
Save company data¶
POST /projects/{project_id}/rows
Body uses column names (not column IDs) as keys under company. All normal (non-tool) columns for the project must be present; tool columns are filled by Kuration.
Request body
{
"company": {
"company_name": "Acme Corporation",
"website": "https://acme.com"
}
}
Example
import requests
project_id = "YOUR_PROJECT_ID"
url = f"https://api.kurationai.com/api/enterprise/projects/{project_id}/rows"
headers = {
"accept": "application/json",
"Content-Type": "application/json",
"kur-api-key": "YOUR_API_KEY",
}
data = {
"company": {
"company_name": "Acme Corporation",
"website": "https://acme.com",
}
}
response = requests.post(url, headers=headers, json=data)
print(response.status_code)
print(response.json())
curl -sS -X POST \
"https://api.kurationai.com/api/enterprise/projects/YOUR_PROJECT_ID/rows" \
-H "accept: application/json" \
-H "Content-Type: application/json" \
-H "kur-api-key: YOUR_API_KEY" \
-d '{"company":{"company_name":"Acme Corporation","website":"https://acme.com"}}'
const projectId = "YOUR_PROJECT_ID";
const url = `https://api.kurationai.com/api/enterprise/projects/${projectId}/rows`;
const response = await fetch(url, {
method: "POST",
headers: {
accept: "application/json",
"Content-Type": "application/json",
"kur-api-key": "YOUR_API_KEY",
},
body: JSON.stringify({
company: {
company_name: "Acme Corporation",
website: "https://acme.com",
},
}),
});
console.log(response.status, await response.json());
Try this request
Response
{
"row_id": "Jxl8KwcI6bAHH8wdQRvs",
"message": "Success",
"error_detail": null
}
Tool columns may execute in the background after the row is created.
Errors
| HTTP | Meaning |
|---|---|
404 |
Project missing or no access. |
403 |
Missing required normal columns or invalid payload (see detail.missing_columns / detail.message). |
List project rows¶
GET /projects/{project_id}/rows
Paginated listing with optional filtering and sorting.
Query parameters
| Param | Type | Default | Constraints | Description |
|---|---|---|---|---|
page |
int | 1 |
≥ 1 | Page number. |
page_size |
int | 50 |
1–100 | Rows per page. |
filter_groups |
string | — | JSON array URL-encoded | Same conceptual schema as filterGroups on the project in the UI. Invalid JSON/shape → 422. |
sort_rules |
string | — | JSON array URL-encoded | e.g. [{"col_id":"<uuid>","direction":"asc","sort_as_number":false}]. Unknown column → 422. |
Basic example
import requests
import json
project_id = "YOUR_PROJECT_ID"
url = f"https://api.kurationai.com/api/enterprise/projects/{project_id}/rows"
headers = {
"accept": "application/json",
"kur-api-key": "YOUR_API_KEY",
}
params = {"page": 1, "page_size": 50}
response = requests.get(url, headers=headers, params=params)
print(json.dumps(response.json(), indent=2))
curl -sS -G \
"https://api.kurationai.com/api/enterprise/projects/YOUR_PROJECT_ID/rows" \
-H "accept: application/json" \
-H "kur-api-key: YOUR_API_KEY" \
--data-urlencode "page=1" \
--data-urlencode "page_size=50"
const projectId = "YOUR_PROJECT_ID";
const base = `https://api.kurationai.com/api/enterprise/projects/${projectId}/rows`;
const params = new URLSearchParams({ page: "1", page_size: "50" });
const response = await fetch(`${base}?${params}`, {
headers: {
accept: "application/json",
"kur-api-key": "YOUR_API_KEY",
},
});
console.log(await response.json());
Try this request
Filter / sort example — Pass JSON as a single query string parameter (properly encoded).
import json
import requests
project_id = "YOUR_PROJECT_ID"
url = f"https://api.kurationai.com/api/enterprise/projects/{project_id}/rows"
headers = {
"accept": "application/json",
"kur-api-key": "YOUR_API_KEY",
}
filter_groups = [{"type": "and", "rules": []}] # replace with real filter groups from your project
sort_rules = [
{"col_id": "4905e83c-bbb5-462f-9917-73d2ed14727c", "direction": "asc", "sort_as_number": False}
]
params = {
"page": 1,
"page_size": 25,
"filter_groups": json.dumps(filter_groups, separators=(",", ":")),
"sort_rules": json.dumps(sort_rules, separators=(",", ":")),
}
# requests will URL-encode values
response = requests.get(url, headers=headers, params=params)
# Compact JSON for query params (no spaces after ":")
FG='[{"type":"and","rules":[]}]'
SR='[{"col_id":"4905e83c-bbb5-462f-9917-73d2ed14727c","direction":"asc","sort_as_number":false}]'
curl -sS -G \
"https://api.kurationai.com/api/enterprise/projects/YOUR_PROJECT_ID/rows" \
-H "accept: application/json" \
-H "kur-api-key: YOUR_API_KEY" \
--data-urlencode "page=1" \
--data-urlencode "page_size=25" \
--data-urlencode "filter_groups=${FG}" \
--data-urlencode "sort_rules=${SR}"
const projectId = "YOUR_PROJECT_ID";
const base = `https://api.kurationai.com/api/enterprise/projects/${projectId}/rows`;
const filterGroups = [{ type: "and", rules: [] }];
const sortRules = [
{
col_id: "4905e83c-bbb5-462f-9917-73d2ed14727c",
direction: "asc",
sort_as_number: false,
},
];
const params = new URLSearchParams({
page: "1",
page_size: "25",
filter_groups: JSON.stringify(filterGroups),
sort_rules: JSON.stringify(sortRules),
});
const response = await fetch(`${base}?${params}`, {
headers: {
accept: "application/json",
"kur-api-key": "YOUR_API_KEY",
},
});
console.log(await response.json());
Try this request (filter + sort)
Response shape (illustrative)
{
"rows": [
{
"id": "4d9ace8e-bdcf-401f-93db-a4b73d12cf13",
"project_id": "98e79cf8-87c9-49b0-9899-58edb6f3c98b",
"company": {
"company_name": {
"name": "company_name",
"value": "Acme Corporation",
"status": "default",
"is_loading": false
},
"website": {
"name": "website",
"value": "https://acme.com",
"status": "default",
"is_loading": false
}
}
}
],
"page": 1,
"page_size": 50,
"total_rows": 243,
"total_pages": 5
}
Errors
| HTTP | Meaning |
|---|---|
404 |
Project missing or no access. |
422 |
Bad filter_groups or sort_rules payload. |
Get row results¶
GET /projects/{project_id}/rows/{row_id}
Returns one row’s company map in the API’s structured format.
Example
import requests
import json
project_id = "YOUR_PROJECT_ID"
row_id = "YOUR_ROW_ID"
url = f"https://api.kurationai.com/api/enterprise/projects/{project_id}/rows/{row_id}"
headers = {
"accept": "application/json",
"kur-api-key": "YOUR_API_KEY",
}
response = requests.get(url, headers=headers)
print(response.status_code)
print(json.dumps(response.json(), indent=2))
curl -sS \
"https://api.kurationai.com/api/enterprise/projects/YOUR_PROJECT_ID/rows/YOUR_ROW_ID" \
-H "accept: application/json" \
-H "kur-api-key: YOUR_API_KEY"
const projectId = "YOUR_PROJECT_ID";
const rowId = "YOUR_ROW_ID";
const url =
`https://api.kurationai.com/api/enterprise/projects/${projectId}/rows/${rowId}`;
const response = await fetch(url, {
headers: {
accept: "application/json",
"kur-api-key": "YOUR_API_KEY",
},
});
console.log(response.status, await response.json());
Try this request
Example response
{
"id": "4d9ace8e-bdcf-401f-93db-a4b73d12cf13",
"project_id": "98e79cf8-87c9-49b0-9899-58edb6f3c98b",
"company": {
"company_name": {
"name": "company_name",
"value": "Acme Corporation",
"status": "default",
"is_loading": false
},
"website": {
"name": "website",
"value": "https://acme.com",
"status": "default",
"is_loading": false
}
}
}
Errors
| HTTP | Meaning |
|---|---|
404 |
Project/row missing, no access, or row deleted. |
Source of truth¶
Server routes for this surface live in enterprise_v3_router.py (GET/POST /projects, row endpoints). When behavior or payloads change, update this page and the in-app integration copy together.