# Tabstack for Agents

> Tabstack gives AI agents and apps finished output from the live web in a single API call. Extract structured data to a schema you define, convert pages to clean Markdown, run cited multi-source research, and automate browser tasks. Every call returns exactly what you asked for, ready to use. Built for developers shipping autonomous agents and those adding web interaction to an existing app or agent. Built by Mozilla, with ephemeral processing, no model training on your data, and robots.txt compliance by default.

This document is everything an agent needs to make a first successful call, on
one surface. Base URL, auth, install, and a runnable example for every endpoint
are all below. For depth, follow the links at the end.

## Base URL and Auth

- Base URL: `https://api.tabstack.ai/v1/`
- Get an API key: https://console.tabstack.ai
- Authenticate every request with an `Authorization: Bearer` header: `Authorization: Bearer $TABSTACK_API_KEY`.
- Using an SDK? Set `TABSTACK_API_KEY` in the environment and it is read by default.

Not using an SDK? Every endpoint is a plain HTTPS POST with a JSON body. Here is the full wire contract for a first call:

```bash
curl https://api.tabstack.ai/v1/extract/json \
  -H "Authorization: Bearer $TABSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://store.example.com/products/aurora-trail-jacket",
    "json_schema": {
      "type": "object",
      "properties": {
        "name": { "type": "string" },
        "price": { "type": "number" }
      }
    }
  }'
```

```typescript
import Tabstack from '@tabstack/sdk'

// Reads TABSTACK_API_KEY from the environment by default.
const client = new Tabstack()
```

```python
from tabstack import Tabstack

# Reads TABSTACK_API_KEY from the environment by default.
client = Tabstack()
```

## Install

```bash
# TypeScript
npm i @tabstack/sdk

# Python
pip install tabstack
```

## Endpoints

| Endpoint | SDK Method | Purpose |
| --- | --- | --- |
| `/extract/json` | `client.extract.json()` | Fetch a URL and return JSON matching a schema you define. |
| `/extract/markdown` | `client.extract.markdown()` | Fetch a URL and return its content as clean Markdown. |
| `/generate/json` | `client.generate.json()` | Fetch a URL, transform its content per instructions, return JSON matching a schema. |
| `/automate` | `client.agent.automate()` | Run an interactive multi-step browser task from a plain-language description. Streams (SSE). |
| `/research` | `client.agent.research()` | Answer a question with cited multi-source synthesis from the live web. Streams (SSE). |

Every example below is runnable as written once `TABSTACK_API_KEY` is set.

### `/extract/json`

Fetch a URL and return JSON matching a schema you define.

```typescript
import Tabstack from '@tabstack/sdk'

const client = new Tabstack()

async function main() {
  try {
    const product = await client.extract.json({
      url: 'https://store.example.com/products/aurora-trail-jacket',
      json_schema: {
        type: 'object',
        properties: {
          name: { type: 'string', description: 'Product name' },
          price: { type: 'number', description: 'Current price' },
          currency: { type: 'string', description: 'ISO 4217 currency code' },
          in_stock: { type: 'boolean', description: 'Whether the product is available' },
          sizes: {
            type: 'array',
            description: 'Per-size availability',
            items: {
              type: 'object',
              properties: {
                size: { type: 'string' },
                in_stock: { type: 'boolean' },
              },
            },
          },
        },
      },
    })
    // Unpopulated fields come back as null rather than failing the call.
    console.log(product)
  } catch (err) {
    console.error(err)
  }
}

main()
```

```python
from tabstack import Tabstack

client = Tabstack()

try:
    product = client.extract.json(
        url="https://store.example.com/products/aurora-trail-jacket",
        json_schema={
            "type": "object",
            "properties": {
                "name": {"type": "string", "description": "Product name"},
                "price": {"type": "number", "description": "Current price"},
                "currency": {"type": "string", "description": "ISO 4217 currency code"},
                "in_stock": {"type": "boolean", "description": "Whether the product is available"},
                "sizes": {
                    "type": "array",
                    "description": "Per-size availability",
                    "items": {
                        "type": "object",
                        "properties": {
                            "size": {"type": "string"},
                            "in_stock": {"type": "boolean"},
                        },
                    },
                },
            },
        },
    )
    print(product)
except Exception as err:
    print(err)
```

### `/extract/markdown`

Fetch a URL and return its content as clean Markdown.

```typescript
import Tabstack from '@tabstack/sdk'

const client = new Tabstack()

async function main() {
  try {
    const { content } = await client.extract.markdown({
      url: 'https://example.com/blog/article',
    })
    console.log(content)
  } catch (err) {
    console.error(err)
  }
}

main()
```

```python
from tabstack import Tabstack

client = Tabstack()

try:
    result = client.extract.markdown(url="https://example.com/blog/article")
    print(result.content)
except Exception as err:
    print(err)
```

### `/generate/json`

Fetch a URL, transform its content per instructions, return JSON matching a schema.

```typescript
import Tabstack from '@tabstack/sdk'

const client = new Tabstack()

async function main() {
  try {
    const data = await client.generate.json({
      url: 'https://news.ycombinator.com',
      instructions:
        'For each story, categorize it and write a one-sentence summary.',
      json_schema: {
        type: 'object',
        properties: {
          summaries: {
            type: 'array',
            items: {
              type: 'object',
              properties: {
                title: { type: 'string' },
                category: { type: 'string' },
                summary: { type: 'string' },
              },
            },
          },
        },
      },
    })
    console.log(data)
  } catch (err) {
    console.error(err)
  }
}

main()
```

```python
from tabstack import Tabstack

client = Tabstack()

try:
    data = client.generate.json(
        url="https://news.ycombinator.com",
        instructions="For each story, categorize it and write a one-sentence summary.",
        json_schema={
            "type": "object",
            "properties": {
                "summaries": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "title": {"type": "string"},
                            "category": {"type": "string"},
                            "summary": {"type": "string"},
                        },
                    },
                }
            },
        },
    )
    print(data)
except Exception as err:
    print(err)
```

### `/automate`

Run an interactive multi-step browser task from a plain-language description. Streams (SSE).

```typescript
import Tabstack from '@tabstack/sdk'

const client = new Tabstack()

async function main() {
  // /automate always streams Server-Sent Events.
  const stream = await client.agent.automate({
    task: 'Find the Enterprise plan price and what is included',
    url: 'https://example.com/pricing',
  })

  try {
    for await (const event of stream) {
      // Read the final answer from task:completed.
      if (event.event === 'task:completed') console.log(event.data.finalAnswer)
      if (event.event === 'error') console.error(event.data.error.message)
      if (event.event === 'done') break // termination: task:validated -> task:completed -> complete -> done
    }
  } catch (err) {
    console.error(err)
  }
}

main()
```

```python
from tabstack import Tabstack

client = Tabstack()

stream = client.agent.automate(
    task="Find the Enterprise plan price and what is included",
    url="https://example.com/pricing",
)

for event in stream:
    # Read the final answer from task:completed, matching the Streaming Model section.
    if event.event == "task:completed":
        print(event.data["final_answer"])
    if event.event == "error":
        print(event.data["error"]["message"])
    if event.event == "done":
        break
```

### `/research`

Answer a question with cited multi-source synthesis from the live web. Streams (SSE).

```typescript
import Tabstack from '@tabstack/sdk'

const client = new Tabstack()

async function main() {
  // /research always streams Server-Sent Events.
  const stream = await client.agent.research({
    query: 'Compare pricing and free tiers across the top 3 analytics platforms',
  })

  try {
    for await (const event of stream) {
      // The final cited report arrives on the complete event.
      if (event.event === 'complete') {
        console.log(event.data.report)
        break
      }
      if (event.event === 'error') {
        console.error(event.data.error.message)
        break
      }
    }
  } catch (err) {
    console.error(err)
  }
}

main()
```

```python
from tabstack import Tabstack

client = Tabstack()

# /research always streams Server-Sent Events.
stream = client.agent.research(
    query="Compare pricing and free tiers across the top 3 analytics platforms",
)

for event in stream:
    if event.event == "complete":
        print(event.data["report"])
        break
    if event.event == "error":
        print(event.data["error"]["message"])
        break
```

## When to Use Which

- **Extract.** Use /extract/json when the page has a known structure and you want it as JSON, or /extract/markdown to read a page as clean Markdown.
- **Generate.** Use /generate/json to transform and summarize page content into a new shape, not just lift what is already there.
- **Research.** Use /research for a cited answer synthesized across multiple live sources.
- **Automate.** Use /automate for an interactive multi-step browser task: navigation, clicks, and forms on pages you do not control.

## Effort, Caching, Geotargeting

- `effort`: `min` (fastest, 1-5s), `standard` (default, 3-15s), `max` (full browser rendering for JS-heavy sites, 15-60s). Available on `/extract/json`, `/extract/markdown`, and `/generate/json`.
- `nocache`: set to `true` to bypass the cache and force fresh retrieval.
- `geo_target`: `{ country: "US" }` using ISO 3166-1 alpha-2 codes. Supported on `/extract/json`, `/extract/markdown`, `/generate/json`, and `/automate`. Not supported on `/research`.

## Streaming Model

`/automate` and `/research` always stream over Server-Sent Events. Iterate the
stream and switch on `event.event`.

- `/automate` termination sequence: `task:validated` -> `task:completed` -> `complete` -> `done`. Read the final answer from the `task:completed` event (`event.data.finalAnswer`). Stop on `done`.
- `/research` emits progress events through its phases, then a single `complete` event carrying the cited report (`event.data.report`). There is no `done` event for research. Stop on `complete`.
- Both surface a top-level `error` event (`event.data.error.message`).

## Error Codes and Retries

| Status | Error Type |
| --- | --- |
| 400 | BadRequest |
| 401 | Authentication |
| 403 | PermissionDenied |
| 404 | NotFound |
| 409 | Conflict |
| 422 | UnprocessableEntity |
| 429 | RateLimit |
| 500+ | InternalServer |

The SDK automatically retries twice with exponential backoff on `408`,
`409`, `429`, and `500+`. Everything else throws a typed error you catch.

## MCP

Hosted MCP endpoint: https://tabstack.stlmcp.com

MCP exposes the non-streaming endpoints. It does not expose the streaming
endpoints `/automate` and `/research`.

## More

- Full documentation and API reference: https://docs.tabstack.ai/
- TypeScript SDK quickstart: https://docs.tabstack.ai/sdks/typescript/quickstart/
- Python SDK quickstart: https://docs.tabstack.ai/sdks/python/quickstart/
- MCP setup: https://docs.tabstack.ai/getting-started/mcp/
- Pilo, the open-source browser engine, on GitHub: https://github.com/mozilla/pilo
