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

# Structured Output

> Get structured, parseable data from AI agent tasks for downstream processing

## Overview

By default, AI agents return natural language responses in markdown format. To get structured data, use the `outputType` parameter to specify the desired format.

**Output Types:**

| Type              | Description                       | Schema Format         |
| ----------------- | --------------------------------- | --------------------- |
| `text`            | Markdown formatted text (default) | Not required          |
| `structured_json` | Validated JSON output             | JSON Schema object    |
| `structured_csv`  | CSV formatted data                | Array of column names |

When using `structured_json` or `structured_csv`, WebRun validates the output against your schema before returning it.

***

## Output Type: text

The default output type returns natural language responses formatted as markdown. No `outputSchema` is required.

### Request Example

```javascript theme={null}
const session = await fetch("https://connect.webrun.ai/start/start-session", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${API_KEY}`
  },
  body: JSON.stringify({
    mode: "default",
    task: {
      prompt: "Give me the latest prices of the top performing cryptos",
      taskId: "3e0f4258511fc909775b9f1a74656004",
      maxDuration: 5,
      maxInputTokens: 400000,
      maxOutputTokens: 200000,
      outputType: "text"
    }
  })
}).then(r => r.json());
```

### Response

The response contains markdown-formatted text:

```json theme={null}
{
  "success": true,
  "type": "task_completed",
  "data": {
    "message": "## Top Cryptocurrency Prices\n\n| Name | Price |\n|------|-------|\n| Bitcoin | $95,360.97 |\n| Ethereum | $3,325.94 |\n...",
    "prompt_tokens": 8500,
    "completion_tokens": 2300,
    "total_tokens": 10800
  }
}
```

***

## Output Type: structured\_json

Use `structured_json` to get validated JSON output. The `outputSchema` parameter accepts a standard JSON Schema object that defines the expected response structure.

### Supported JSON Schema Types

* `string` - Text values
* `number` - Numeric values (integers and decimals)
* `boolean` - True/false values
* `object` - Nested objects with defined properties
* `array` - Arrays with defined item structure

### Request Example

```javascript theme={null}
const session = await fetch("https://connect.webrun.ai/start/start-session", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${API_KEY}`
  },
  body: JSON.stringify({
    mode: "default",
    task: {
      prompt: "Give me the latest prices of the top performing cryptos",
      taskId: "2444180416485ba80be9f12ef51134dc",
      maxDuration: 60,
      maxInputTokens: 4000,
      maxOutputTokens: 2000,
      outputType: "structured_json",
      outputSchema: {
        type: "object",
        properties: {
          top: {
            type: "array",
            items: {
              type: "object",
              properties: {
                name: { type: "string" },
                price: { type: "string" }
              }
            }
          }
        },
        additionalProperties: false
      }
    }
  })
}).then(r => r.json());
```

### Response

The response is guaranteed to match your schema:

```json theme={null}
{
  "success": true,
  "type": "task_completed",
  "data": {
    "message": "{\"top\":[{\"name\":\"Bitcoin\",\"price\":\"$95,187.71\"},{\"name\":\"Ethereum\",\"price\":\"$3,319.17\"},{\"name\":\"Tether\",\"price\":\"$0.9995\"},{\"name\":\"XRP\",\"price\":\"$2.15\"},{\"name\":\"BNB\",\"price\":\"$942.50\"},{\"name\":\"Solana\",\"price\":\"$145.42\"},{\"name\":\"USDC\",\"price\":\"$0.9997\"},{\"name\":\"TRON\",\"price\":\"$0.3064\"},{\"name\":\"Dogecoin\",\"price\":\"$0.1482\"},{\"name\":\"Cardano\",\"price\":\"$0.4199\"}]}",
    "prompt_tokens": 8500,
    "completion_tokens": 2300,
    "total_tokens": 10800
  }
}
```

<Note>
  When using `outputType: "structured_json"`, the response is guaranteed to match your schema. The `data.message` field contains a valid JSON string that can be directly parsed.
</Note>

***

## Output Type: structured\_csv

Use `structured_csv` to get CSV formatted output. The `outputSchema` parameter accepts an array of column names that define the CSV structure.

### Request Example

```javascript theme={null}
const session = await fetch("https://connect.webrun.ai/start/start-session", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${API_KEY}`
  },
  body: JSON.stringify({
    mode: "default",
    task: {
      prompt: "Give me the latest prices of the top performing cryptos",
      taskId: "3e0f4258511fc909775b9f1a74656004",
      maxDuration: 5,
      maxInputTokens: 400000,
      maxOutputTokens: 200000,
      outputType: "structured_csv",
      outputSchema: ["name", "price"]
    }
  })
}).then(r => r.json());
```

### Response

The response contains CSV formatted data with the specified columns:

```json theme={null}
{
  "success": true,
  "type": "task_completed",
  "data": {
    "message": "name,price\nBitcoin,$95,360.97\nEthereum,$3,325.94\nTether,$0.9994\nXRP,$2.16\nBNB,$943.70\nSolana,$145.38\nUSDC,$0.9996\nTRON,$0.3070\nDogecoin,$0.1483\nCardano,$0.4220",
    "prompt_tokens": 8500,
    "completion_tokens": 2300,
    "total_tokens": 10800
  }
}
```

### Parsing CSV Response

```javascript theme={null}
const result = await pollForResult(sessionId, taskId);
const csv = result.data.message;
const lines = csv.trim().split('\n');
const headers = lines[0].split(',');
const data = lines.slice(1).map(line => {
  const values = line.match(/(".*?"|[^,]+)(?=\s*,|\s*$)/g);
  const obj = {};
  headers.forEach((header, i) => {
    obj[header.trim()] = values[i]?.replace(/^"|"$/g, '').trim();
  });
  return obj;
});

console.log(data);
// [
//   { name: "Bitcoin", price: "$95,360.97" },
//   { name: "Ethereum", price: "$3,325.94" },
//   ...
// ]
```

<Note>
  When using `outputType: "structured_csv"`, the `outputSchema` must be an array of column name strings. The first row of the output will be the header row containing these column names.
</Note>

***

## Webhook Integration

Combine structured output with [webhooks](/usage-guides/webhooks) to automatically deliver validated data to your systems when tasks complete.

```javascript theme={null}
const session = await fetch("https://connect.webrun.ai/start/start-session", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${API_KEY}`
  },
  body: JSON.stringify({
    mode: "default",
    task: {
      prompt: "Extract product information from the page",
      startingUrl: "https://example.com/product",
      outputType: "structured_json",
      outputSchema: {
        type: "object",
        properties: {
          title: { type: "string" },
          price: { type: "number" },
          inStock: { type: "boolean" }
        },
        required: ["title", "price"],
        additionalProperties: false
      },
      webhook: {
        name: "Product Webhook",
        url: "https://api.mysite.com/products",
        auth: "Bearer token123",
        submittedData: "ai_response"
      }
    }
  })
}).then(r => r.json());
```

<Tip>
  Use `submittedData: "ai_response"` to receive only the AI's output (text, structured\_json, or structured\_csv). See the [Webhooks guide](/usage-guides/webhooks) for complete documentation.
</Tip>

***

## Legacy Approach (Deprecated)

<Warning>
  The approach of embedding schema descriptions in `prompt` is deprecated. Use the native `outputType` and `outputSchema` parameters instead for guaranteed validation and consistent output.
</Warning>

The examples below show the legacy pattern for reference. New implementations should use the [Native JSON Schema Support](#native-json-schema-support) documented above.

***

## JSON Output Pattern (Legacy)

The most common pattern for structured output is requesting JSON in your task description.

### Basic Example

```javascript theme={null}
const session = await fetch("https://connect.webrun.ai/start/start-session", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${API_KEY}`
  },
  body: JSON.stringify({
    task: {
      prompt: `Go to amazon.com and search for "wireless keyboard".
      Return the results as a JSON array with this format:
      [
        { "title": "product name", "price": "19.99", "rating": "4.5" }
      ]`,
      startingUrl: "https://amazon.com"
    }
  })
}).then(r => r.json());
```

**Expected Response:**

```json theme={null}
{
  "success": true,
  "type": "task_completed",
  "data": {
    "message": "[{\"title\":\"Logitech K380\",\"price\":\"29.99\",\"rating\":\"4.5\"},{\"title\":\"Arteck Wireless Keyboard\",\"price\":\"19.99\",\"rating\":\"4.3\"}]",
    "prompt_tokens": 8500,
    "completion_tokens": 2300,
    "total_tokens": 10800
  }
}
```

### Parsing the Result

```javascript theme={null}
async function getProductData(sessionId, taskId) {
  // Poll for result
  const result = await pollForResult(sessionId, taskId);

  if (result.type === "task_completed") {
    try {
      // Parse JSON from message
      const products = JSON.parse(result.data.message);
      return products;
    } catch (error) {
      console.error("Failed to parse JSON:", error);
      console.log("Raw message:", result.data.message);
      return null;
    }
  }

  return null;
}

// Usage
const products = await getProductData(session.sessionId, task.taskId);
console.log(products);
// [
//   { title: "Logitech K380", price: "29.99", rating: "4.5" },
//   { title: "Arteck Wireless Keyboard", price: "19.99", rating: "4.3" }
// ]
```

***

## Array Results

Request arrays for lists of items.

### Example: Product Search

```javascript theme={null}
const prompt = `
Search Google for "best mechanical keyboards 2024".
Extract the top 5 results and return as a JSON array:
[
  {
    "title": "page title",
    "url": "https://...",
    "snippet": "brief description"
  }
]
`;

const task = await fetch("https://connect.webrun.ai/start/send-message", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${API_KEY}`
  },
  body: JSON.stringify({
    sessionId: session.sessionId,
    message: {
      actionType: "newTask",
      newState: "start",
      prompt
    }
  })
}).then(r => r.json());
```

***

## Complex Nested Structures

Request nested JSON for complex data.

### Example: E-commerce Product Details

```javascript theme={null}
const prompt = `
Go to the Amazon product page at [URL].
Extract the following information and return as JSON:
{
  "product": {
    "title": "product name",
    "price": "19.99",
    "currency": "USD",
    "inStock": true,
    "rating": {
      "stars": 4.5,
      "count": 1234
    },
    "images": ["url1", "url2"],
    "features": ["feature 1", "feature 2"],
    "specifications": {
      "brand": "...",
      "model": "...",
      "dimensions": "..."
    }
  }
}
`;

const result = await sendTaskAndWait(session.sessionId, prompt);
const productData = JSON.parse(result.data.message);

console.log(productData.product.title);
console.log(productData.product.rating.stars);
```

***

## Validation with Zod (TypeScript)

Use Zod for runtime validation and type safety.

### Install Zod

```bash theme={null}
npm install zod
```

### Define Schema

```typescript theme={null}
import { z } from "zod";

// Define expected structure
const ProductSchema = z.object({
  title: z.string(),
  price: z.string(),
  rating: z.string(),
  url: z.string().url(),
  inStock: z.boolean()
});

const ProductArraySchema = z.array(ProductSchema);

// Type inference
type Product = z.infer<typeof ProductSchema>;
```

### Validate Response

```typescript theme={null}
async function getValidatedProducts(sessionId: string, taskId: string): Promise<Product[]> {
  const result = await pollForResult(sessionId, taskId);

  if (result.type !== "task_completed") {
    throw new Error("Task not completed");
  }

  try {
    // Parse JSON
    const rawData = JSON.parse(result.data.message);

    // Validate with Zod
    const products = ProductArraySchema.parse(rawData);

    return products; // Type-safe products array
  } catch (error) {
    if (error instanceof z.ZodError) {
      console.error("Validation failed:", error.errors);
      throw new Error(`Invalid product data: ${error.message}`);
    }
    throw error;
  }
}

// Usage (TypeScript knows the shape)
const products = await getValidatedProducts(sessionId, taskId);
products.forEach(product => {
  console.log(product.title); // Type-safe
  console.log(product.price); // Type-safe
});
```

***

## Handling Parse Errors

Sometimes the AI returns malformed JSON. Implement fallback strategies:

### Strategy 1: Extract JSON from Text

```javascript theme={null}
function extractJSON(text) {
  // Try direct parse first
  try {
    return JSON.parse(text);
  } catch (error) {
    // Try to find JSON within text
    const jsonMatch = text.match(/\[[\s\S]*\]|\{[\s\S]*\}/);
    if (jsonMatch) {
      try {
        return JSON.parse(jsonMatch[0]);
      } catch (innerError) {
        console.error("Failed to extract JSON:", innerError);
      }
    }
  }

  return null;
}

// Usage
const result = await pollForResult(sessionId, taskId);
const data = extractJSON(result.data.message);

if (data) {
  console.log("Parsed:", data);
} else {
  console.log("Could not parse, raw response:", result.data.message);
}
```

### Strategy 2: Request Re-formatting

```javascript theme={null}
async function getStructuredDataWithRetry(sessionId, originalTaskId) {
  const result = await pollForResult(sessionId, originalTaskId);

  // Try to parse
  try {
    return JSON.parse(result.data.message);
  } catch (error) {
    console.log("Initial parse failed, requesting reformatting...");

    // Send follow-up task to reformat
    const reformatTask = await fetch("https://connect.webrun.ai/start/send-message", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${API_KEY}`
      },
      body: JSON.stringify({
        sessionId,
        message: {
          actionType: "newTask",
          newState: "start",
          prompt: `Take the previous response and format it as valid JSON: ${result.data.message}`
        }
      })
    }).then(r => r.json());

    const reformatResult = await pollForResult(sessionId, reformatTask.taskId);
    return JSON.parse(reformatResult.data.message);
  }
}
```

***

## Best Practices

### 1. Be Specific in Schema Description

```javascript theme={null}
// Bad - vague
prompt: "Return product info as JSON"

// Good - explicit schema
prompt: `
Return product information as JSON with this exact structure:
{
  "title": "string",
  "price": "number as string (e.g., '29.99')",
  "inStock": "boolean (true/false)",
  "rating": "number as string (e.g., '4.5')"
}
`
```

### 2. Request Array Length Limits

Prevent unexpectedly large responses:

```javascript theme={null}
prompt: `
Search for wireless keyboards and return the top 10 results (no more than 10) as JSON array:
[
  { "title": "...", "price": "...", "url": "..." }
]
`
```

### 3. Specify Data Types Clearly

```javascript theme={null}
prompt: `
Return product data as JSON:
{
  "title": "string",
  "price": "string in format '19.99' (no currency symbol)",
  "inStock": "boolean (true or false, not string)",
  "rating": "number (e.g., 4.5, not string)",
  "reviewCount": "integer (e.g., 123, not string)"
}
`
```

### 4. Handle Missing Data

Specify how to handle missing fields:

```javascript theme={null}
prompt: `
Return product data as JSON:
{
  "title": "string (required)",
  "price": "string or null if not available",
  "rating": "number or null if no ratings",
  "inStock": "boolean or null if unknown"
}
`
```

### 5. Use TypeScript for Type Safety

```typescript theme={null}
interface Product {
  title: string;
  price: string | null;
  rating: number | null;
  inStock: boolean | null;
}

async function getProducts(sessionId: string, taskId: string): Promise<Product[]> {
  const result = await pollForResult(sessionId, taskId);
  const products: Product[] = JSON.parse(result.data.message);
  return products;
}
```

***

## Complete Example: Product Comparison

```javascript theme={null}
const API_KEY = "YOUR_API_KEY";
const BASE = "https://connect.webrun.ai";

async function compareProducts(searchQuery) {
  // 1. Create session
  const session = await fetch(`${BASE}/start/start-session`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${API_KEY}`
    },
    body: JSON.stringify({
      task: {
        prompt: `
          Go to Amazon and search for "${searchQuery}".
          Return the top 5 results as a JSON array with this structure:
          [
            {
              "title": "product title",
              "price": "price as string without currency symbol (e.g., '29.99')",
              "rating": "average rating as number (e.g., 4.5)",
              "reviewCount": "number of reviews as integer",
              "url": "product URL",
              "isPrime": "boolean indicating Prime eligibility"
            }
          ]
          Return ONLY the JSON array, no additional text.
        `,
        startingUrl: "https://amazon.com",
        terminateOnCompletion: true
      }
    })
  }).then(r => r.json());

  console.log("Session created:", session.sessionId);

  // 2. Poll for result
  const result = await pollForResult(session.sessionId, session.taskId);

  // 3. Parse and validate
  try {
    const products = JSON.parse(result.data.message);

    // Validate structure
    if (!Array.isArray(products)) {
      throw new Error("Expected array of products");
    }

    // Process results
    const processed = products.map(p => ({
      ...p,
      price: parseFloat(p.price),
      pricePerStar: (parseFloat(p.price) / p.rating).toFixed(2)
    }));

    // Sort by value (price per rating star)
    processed.sort((a, b) => a.pricePerStar - b.pricePerStar);

    console.log("Best value products:");
    processed.forEach((p, i) => {
      console.log(`${i + 1}. ${p.title}`);
      console.log(`   Price: $${p.price}, Rating: ${p.rating} (${p.reviewCount} reviews)`);
      console.log(`   Value: $${p.pricePerStar} per star`);
      console.log(`   URL: ${p.url}`);
      console.log();
    });

    return processed;
  } catch (error) {
    console.error("Failed to parse product data:", error);
    console.log("Raw response:", result.data.message);
    return null;
  }
}

async function pollForResult(sessionId, taskId) {
  const maxAttempts = 60;
  const interval = 2000;

  for (let i = 0; i < maxAttempts; i++) {
    const res = await fetch(`${BASE}/task/${sessionId}/${taskId}`, {
      headers: { "Authorization": `Bearer ${API_KEY}` }
    });
    const data = await res.json();

    if (data.type === "task_completed") return data;
    if (data.type === "guardrail_trigger") throw new Error(`Guardrail: ${data.data.value}`);
    if (data.status === "failed") throw new Error(data.error);

    if (data.pending) {
      await new Promise(r => setTimeout(r, interval));
      continue;
    }

    return data;
  }

  throw new Error("Polling timeout");
}

// Run comparison
compareProducts("wireless mechanical keyboard");
```

***

## CSV Output

For tabular data, CSV can be simpler than JSON:

```javascript theme={null}
const prompt = `
Search Amazon for "standing desk" and return the top 10 results as CSV with these columns:
title,price,rating,reviewCount,url

Example:
"FlexiSpot Standing Desk","299.99","4.5","2341","https://..."
"FEZIBO Electric Desk","249.99","4.3","1823","https://..."

Return ONLY the CSV data with header row, no additional text.
`;

// Parse CSV
const result = await pollForResult(sessionId, taskId);
const csv = result.data.message;
const lines = csv.trim().split('\n');
const headers = lines[0].split(',');
const products = lines.slice(1).map(line => {
  const values = line.match(/(".*?"|[^,]+)(?=\s*,|\s*$)/g);
  const obj = {};
  headers.forEach((header, i) => {
    obj[header.trim()] = values[i].replace(/^"|"$/g, '').trim();
  });
  return obj;
});

console.log(products);
```

***

<Accordion title="Related Guides">
  <CardGroup cols={2}>
    <Card title="Webhooks" icon="webhook" href="/usage-guides/webhooks">
      Receive automated notifications when tasks complete
    </Card>

    <Card title="Multi-Task Workflows" icon="list-check" href="/usage-guides/multi-task-workflows">
      Chain tasks to build complex data pipelines
    </Card>
  </CardGroup>
</Accordion>
