> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kadoa.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create workflows with the SDK & API

> Create workflows from Python, Node.js, or the REST API

Create workflows from code when you build many at once or run them from your own pipeline. Kadoa's agent builds each one, the same as in the dashboard.

## Before you begin

* A team [API key](https://docs.kadoa.com/docs/api-keys.md). Install the SDK with `npm install @kadoa/node-sdk`, `yarn add @kadoa/node-sdk`, or `uv add kadoa-sdk`.

The SDK and API examples on this page start from an authenticated client:

```typescript Node SDK theme={null}
import { KadoaClient } from '@kadoa/node-sdk';

const client = new KadoaClient({
  apiKey: 'YOUR_API_KEY'
});

const status = await client.status();
console.log(status);
console.log(status.user);
```

```python Python SDK theme={null}
import asyncio
from kadoa_sdk import KadoaClient, KadoaClientConfig

client = KadoaClient(KadoaClientConfig(api_key="YOUR_API_KEY"))

status = asyncio.run(client.status())
print(status)
print(status.user)
```

```javascript REST API theme={null}
const headers = {
  "x-api-key": "YOUR_API_KEY",
  "Content-Type": "application/json"
};
```

## Choose the source

One workflow covers one website. See the [source rules](https://docs.kadoa.com/docs/ui/workflows/sources.md#source-rules).

Pass the URLs in `urls`. Every example on this page sets it.

If the URLs span several websites, the API rejects the request with `422 SOURCES_MULTIPLE_SITES`. Create one workflow per website instead, optionally from a shared [template](https://docs.kadoa.com/docs/templates.md).

## Write the prompt

A prompt drives every workflow. It describes in plain language what to extract and how to get there. The agent follows it on its own and handles navigation, pagination, clicks, forms, and file downloads. For multi-step extractions, write clear steps in order. See [Prompts](https://docs.kadoa.com/docs/workflows/prompts.md#writing-good-prompts) for patterns that work.

Pass the prompt in `userPrompt`:

```typescript Node SDK theme={null}
const workflow = await client
  .extract({
    urls: ["https://sandbox.kadoa.com/ecommerce"],
    name: "Product Catalog",
    userPrompt: "Extract all products. Click 'Next' to continue through pagination. For each product, open the detail page and extract the full description and specifications.",
    extraction: (builder) =>
      builder
        .entity("Product")
        .field("title", "Product name", "STRING", {
          example: "Wireless Headphones",
        })
        .field("price", "Product price", "MONEY")
        .field("description", "Full description", "STRING", {
          example: "Over-ear headphones with active noise cancellation",
        })
        .field("specifications", "Technical specs", "STRING", {
          example: "Bluetooth 5.3, 30-hour battery life",
        }),
  })
  .create();

const result = await workflow.run({ limit: 50 });
```

```python Python SDK theme={null}
from kadoa_sdk import (
    ExtractOptions,
    FieldOptions,
    KadoaClient,
    KadoaClientConfig,
    RunWorkflowOptions,
)

client = KadoaClient(KadoaClientConfig(api_key="YOUR_API_KEY"))

workflow = (
    client.extract(
        ExtractOptions(
            urls=["https://sandbox.kadoa.com/ecommerce"],
            name="Product Catalog",
            user_prompt="Extract all products. Click 'Next' to continue through pagination. For each product, open the detail page and extract the full description and specifications.",
            extraction=lambda builder: builder.entity("Product")
            .field("title", "Product name", "STRING", FieldOptions(example="Wireless Headphones"))
            .field("price", "Product price", "MONEY")
            .field(
                "description",
                "Full description",
                "STRING",
                FieldOptions(example="Over-ear headphones with active noise cancellation"),
            )
            .field(
                "specifications",
                "Technical specs",
                "STRING",
                FieldOptions(example="Bluetooth 5.3, 30-hour battery life"),
            ),
        )
    )
    .create()
)

result = workflow.run(RunWorkflowOptions(limit=50))
```

```javascript REST API theme={null}
// POST https://api.kadoa.com/v4/workflows
{
  "urls": ["https://sandbox.kadoa.com/ecommerce"],
  "name": "Product Catalog",
  "userPrompt": "Extract all products. Click 'Next' to continue through pagination. For each product, open the detail page and extract the full description and specifications.",
  "entity": "Product",
  "fields": [
    { "name": "title", "dataType": "STRING", "description": "Product name" },
    { "name": "price", "dataType": "MONEY", "description": "Product price" },
    { "name": "description", "dataType": "STRING", "description": "Full description" },
    { "name": "specifications", "dataType": "STRING", "description": "Technical specs" }
  ]
}
```

## Auto-detect fields

Give Kadoa a source without naming fields. Kadoa suggests a data schema and extracts all relevant data from the page.

Pass only the URL:

```typescript Node SDK theme={null}
// SDK: AI automatically detects and extracts data
const result = await client.extraction.run({
  urls: ["https://sandbox.kadoa.com/ecommerce"]
});

console.log(result.data);
```

```python Python SDK theme={null}
from kadoa_sdk import ExtractionOptions, KadoaClient, KadoaClientConfig

client = KadoaClient(KadoaClientConfig(api_key="YOUR_API_KEY"))

# SDK: AI automatically detects and extracts data
result = client.extraction.run(
    ExtractionOptions(
        urls=["https://sandbox.kadoa.com/ecommerce"]
    )
)

print(result.data)
```

```javascript REST API theme={null}
// API: Describe the extraction in plain language
// POST https://api.kadoa.com/v4/workflows
{
  "urls": ["https://sandbox.kadoa.com/ecommerce"],
  "name": "Auto Product Extraction",
  "userPrompt": "Extract all product information including name, price, availability, and any other relevant details"
}

// Or define a minimal schema:
{
  "urls": ["https://sandbox.kadoa.com/ecommerce"],
  "name": "Auto Product Extraction",
  "entity": "Product",
  "fields": [
    {
      "name": "data",
      "dataType": "STRING",
      "description": "Extract all product information"
    }
  ]
}
```

## Define the fields

Name each field yourself for precise control over the output.

Define the entity and its fields:

```typescript Node SDK theme={null}
const workflow = await client
  .extract({
    urls: ["https://sandbox.kadoa.com/ecommerce"],
    name: "Structured Product Extraction",
    extraction: (builder) =>
      builder
        .entity("Product")
        .field("title", "Product name", "STRING", {
          example: "iPhone 15 Pro",
        })
        .field("price", "Price in USD", "MONEY")
        .field("inStock", "Availability", "BOOLEAN")
        .field("rating", "Rating 1-5", "NUMBER")
        .field("releaseDate", "Launch date", "DATE"),
  })
  .create();

const result = await workflow.run({ limit: 10 });

// Use destructuring for cleaner access
const { data } = await result.fetchData({});
console.log(data);
```

```python Python SDK theme={null}
from kadoa_sdk import (
    ExtractOptions,
    FieldOptions,
    KadoaClient,
    KadoaClientConfig,
    RunWorkflowOptions,
)

client = KadoaClient(KadoaClientConfig(api_key="YOUR_API_KEY"))

workflow = (
    client.extract(
        ExtractOptions(
            urls=["https://sandbox.kadoa.com/ecommerce"],
            name="Structured Product Extraction",
            extraction=lambda builder: builder.entity("Product")
            .field("title", "Product name", "STRING", FieldOptions(example="iPhone 15 Pro"))
            .field("price", "Price in USD", "MONEY")
            .field("inStock", "Availability", "BOOLEAN")
            .field("rating", "Rating 1-5", "NUMBER")
            .field("releaseDate", "Launch date", "DATE"),
        )
    )
    .create()
)

result = workflow.run(RunWorkflowOptions(limit=10))

# Use destructuring for cleaner access
response = result.fetch_data({})
print(response.data)
```

```javascript REST API theme={null}
// POST https://api.kadoa.com/v4/workflows
{
  "urls": ["https://sandbox.kadoa.com/ecommerce"],
  "name": "Structured Product Extraction",
  "entity": "Product",
  "fields": [
    {
      "name": "title",
      "dataType": "STRING",
      "description": "Product name"
    },
    {
      "name": "price",
      "dataType": "MONEY",
      "description": "Price in USD"
    },
    {
      "name": "inStock",
      "dataType": "BOOLEAN",
      "description": "Availability"
    },
    {
      "name": "rating",
      "dataType": "NUMBER",
      "description": "Rating 1-5"
    },
    {
      "name": "releaseDate",
      "dataType": "DATE",
      "description": "Launch date"
    }
  ]
}
```

To reuse a saved schema across workflows, see [Schemas](https://docs.kadoa.com/docs/workflows/schemas.md).

Available data types: `STRING`, `NUMBER`, `BOOLEAN`, `DATE`, `DATETIME`, `MONEY`, `IMAGE`, `LINK`, `OBJECT`, `ARRAY`. See [Data types](https://docs.kadoa.com/docs/workflows/schemas.md#data-types) for what each one holds.

## Classify records

Sort each record into one of the classes you define, such as the tone or topic of an article.

Add a `classify` field with a title and definition for each class:

```typescript Node SDK theme={null}
const workflow = await client
  .extract({
    urls: ["https://sandbox.kadoa.com/news"],
    name: "Article Classifier",
    extraction: (builder) =>
      builder
        .entity("Article")
        .field("title", "Headline", "STRING", {
          example: "Tech Company Announces New Product",
        })
        .field("content", "Article text", "STRING", {
          example: "The article discusses the latest innovations...",
        })
        .classify("sentiment", "Content tone", [
          { title: "Positive", definition: "Optimistic tone" },
          { title: "Negative", definition: "Critical tone" },
          { title: "Neutral", definition: "Balanced tone" },
        ])
        .classify("category", "Article topic", [
          { title: "Technology", definition: "Tech news" },
          { title: "Business", definition: "Business news" },
          { title: "Politics", definition: "Political news" },
        ]),
  })
  .create();
//Note: 'limit' here is limiting number of extracted records not fetched
const result = await workflow.run({ limit: 10, variables: {} });
console.log(result.jobId);
const data = result.fetchData({ limit: 10 });
console.log(data);
```

```python Python SDK theme={null}
from kadoa_sdk import (
    ExtractOptions,
    FieldOptions,
    KadoaClient,
    KadoaClientConfig,
    RunWorkflowOptions,
)

client = KadoaClient(KadoaClientConfig(api_key="YOUR_API_KEY"))

workflow = (
    client.extract(
        ExtractOptions(
            urls=["https://sandbox.kadoa.com/news"],
            name="Article Classifier",
            extraction=lambda builder: builder.entity("Article")
            .field(
                "title",
                "Headline",
                "STRING",
                FieldOptions(example="Tech Company Announces New Product"),
            )
            .field(
                "content",
                "Article text",
                "STRING",
                FieldOptions(example="The article discusses the latest innovations..."),
            )
            .classify(
                "sentiment",
                "Content tone",
                [
                    {"title": "Positive", "definition": "Optimistic tone"},
                    {"title": "Negative", "definition": "Critical tone"},
                    {"title": "Neutral", "definition": "Balanced tone"},
                ],
            )
            .classify(
                "category",
                "Article topic",
                [
                    {"title": "Technology", "definition": "Tech news"},
                    {"title": "Business", "definition": "Business news"},
                    {"title": "Politics", "definition": "Political news"},
                ],
            ),
        )
    )
    .create()
)
# Note: 'limit' here is limiting number of extracted records not fetched
result = workflow.run(RunWorkflowOptions(limit=10, variables={}))
print(result.job_id)
data = result.fetch_data({"limit": 10})
print(data)
```

## Limit the rows per run

A row limit caps how many records each run extracts. Without one, the workflow extracts all rows.

Pass `limit` when you run the workflow, as the examples on this page do. It limits the records extracted, not the records fetched afterward. In the REST API, set `limit` in the create request.

## Schedule the workflow

A new workflow runs once unless you give it a schedule.

Call `setInterval()` in the Node SDK or `set_interval()` in the Python SDK. In the REST API, pass `interval`, and `schedules` for cron expressions.

See [Scheduling](https://docs.kadoa.com/docs/workflows/scheduling.md) for every interval, cron examples, and timezones.

## Create from a template

If your team has [templates](https://docs.kadoa.com/docs/templates.md), start the workflow from one. The schema, data quality rules, notifications, frequency, and record limit come from the template. The workflow links to the template's latest version, and the template-managed parts become read-only on the workflow.

You can still add instructions for this source. Kadoa keeps them separate from the template's setup prompt, so later template updates do not overwrite them.

Pass `templateId`. Put instructions for this source in `userPrompt`. Omit `templateVersion` to use the template's latest published version.

```javascript REST API theme={null}
// POST https://api.kadoa.com/v4/workflows
{
  "urls": ["https://example.com/careers"],
  "templateId": "template-123",
  "userPrompt": "Open each posting for the full description. Skip internships."
}
```

## Skip the preview

By default, a new workflow stops at a preview so you can review its data. `bypassPreview` skips that review and activates the workflow immediately. Use it only when you trust the configuration.

```typescript Node SDK theme={null}
const workflow = await client
  .extract({
    urls: ["https://sandbox.kadoa.com/magic"],
    name: "Direct Activation",
    extraction: (builder) =>
      builder
        .entity("Product")
        .field("title", "Title", "STRING", { example: "example" }),
  })
  .bypassPreview() // Skip review step
  .create();

// Workflow is immediately active
```

```python Python SDK theme={null}
from kadoa_sdk import ExtractOptions, FieldOptions, KadoaClient, KadoaClientConfig

client = KadoaClient(KadoaClientConfig(api_key="YOUR_API_KEY"))

workflow = (
    client.extract(
        ExtractOptions(
            urls=["https://sandbox.kadoa.com/magic"],
            name="Direct Activation",
            extraction=lambda builder: builder.entity("Product").field(
                "title", "Title", "STRING", FieldOptions(example="example")
            ),
        )
    )
    .bypass_preview()  # Skip review step
    .create()
)

# Workflow is immediately active
```

```bash REST API theme={null}
curl -X POST https://api.kadoa.com/v4/workflows \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "bypassPreview": true,
    "urls": ["https://sandbox.kadoa.com/magic"],
    "entity": "Product",
    "fields": [{"name": "title", "dataType": "STRING", "description": "Title"}]
  }'
```

## Related

* [Create workflow API reference](https://docs.kadoa.com/api-reference/workflows/create-a-new-workflow.md)
* [Scheduling](https://docs.kadoa.com/docs/workflows/scheduling.md): intervals, on-demand runs, and run status.
* [Manage workflows](https://docs.kadoa.com/docs/workflows/manage.md): list, update, pause, and delete workflows.
* [Schemas](https://docs.kadoa.com/docs/workflows/schemas.md): create and reuse schemas.
* [Export data](https://docs.kadoa.com/docs/export-data.md): fetch the extracted data.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.