---
title: "Scrape Linear with OpenAI Agents SDK | EnConvert"
description: "Scrape Linear pages into an OpenAI Agents SDK pipeline with EnConvert Perceive, returning clean Markdown with navigation and sidebars stripped in a batch call"
canonical: "https://www.enconvert.com/solutions/scrape-linear-with-openai-agents-sdk-enconvert"
locale: "en"
---

# How to Scrape Linear with OpenAI Agents SDK

A developer building an OpenAI Agents SDK pipeline that reads Linear pages needs clean page text before passing it into an agent tool. Linear pages mix content with a project sidebar, issue status badges, label filters, priority indicators, comment threads, an activity log, keyboard shortcut hints, member avatars, and cycle panels, and scraping them by hand requires CSS selectors that break when Linear updates its layout. EnConvert Perceive renders up to 10 Linear pages in a single batch call and returns clean Markdown for each one, with navigation and sidebars stripped by default. The extracted Markdown is ready to pass into an OpenAI Agents SDK function tool with no HTML parsing.

## Example

```python
import os
import requests
from agents import Agent, Runner, function_tool

# The OpenAI Agents SDK reads OPENAI_API_KEY from the environment

BASE_URL = "https://api.enconvert.com/v2"
headers = {
    "X-API-Key": os.environ["ENCONVERT_API_KEY"],
    "Content-Type": "application/json",
}

LINEAR_URLS = [url.strip() for url in os.environ["LINEAR_PAGE_URLS"].split(",")]  # public Linear page URLs

@function_tool
def read_linear_pages(urls: list[str]) -> str:
    """Reads publicly accessible Linear pages and returns the main content of each page as clean Markdown, labelled with its source URL."""
    if len(urls) > 10:
        raise ValueError("Send 10 URLs or fewer for an inline response")

    response = requests.post(
        f"{BASE_URL}/perceive/batch",
        headers=headers,
        json={"urls": urls, "options": {"outputs": ["markdown"]}},
        timeout=300,
    )

    if response.status_code == 402:
        raise RuntimeError("Monthly ops allowance exhausted")
    response.raise_for_status()

    sections = []
    for result in response.json()["items"]:
        page_url = result["url"]
        if result.get("status") != "completed":
            sections.append(f"Source: {page_url}\nCould not be read ({result.get('status')}).")
            continue
        if result.get("is_blocked"):
            sections.append(f"Source: {page_url}\nCould not be read (blocked).")
            continue
        render_quality = result.get("render_quality", 1.0)
        if render_quality < 0.40:
            sections.append(f"Source: {page_url}\nCould not be read (render quality {render_quality}).")
            continue
        markdown = requests.get(
            result["outputs"]["markdown"]["url"], timeout=60
        )
        markdown.raise_for_status()
        sections.append(f"Source: {page_url}\n\n{markdown.text}")

    return "\n\n---\n\n".join(sections) or "No pages were returned."

agent = Agent(
    name="Linear assistant",
    instructions=(
        "You answer questions about Linear. Use read_linear_pages to read the pages "
        "you need, answer only from what they say, and cite the source URL for every statement."
    ),
    tools=[read_linear_pages],
    model="gpt-4.1",
)

result = Runner.run_sync(
    agent,
    (
        f"Read these Linear pages: {', '.join(LINEAR_URLS)}. "
        "Summarise what each page covers and how they relate."
    ),
)
print(result.final_output)
```

## Output

Perceive returns clean Markdown for each URL in the batch, with navigation and sidebars stripped by default, delivered as pre-signed URLs that you fetch once per page. Scraping Linear by hand needs CSS selectors that break when the layout changes, and passing raw Linear HTML into an OpenAI Agents SDK tool fills the agent context with the project sidebar, status badges, and interface chrome that add noise to every tool call.

## Get started

[Get free API key — 500 ops per month, no card required.](https://www.enconvert.com/auth?mode=signup)

## Related solutions

- [How to Scrape Wikipedia with LlamaIndex](https://www.enconvert.com/solutions/scrape-wikipedia-with-llamaindex-enconvert.md)
- [How to Ingest Markdown into LangChain with EnConvert](https://www.enconvert.com/solutions/ingest-markdown-into-langchain-with-heading-context.md)
- [Best JigsawStack Alternative for Structured Data Extraction](https://www.enconvert.com/solutions/jigsawstack-alternative-for-structured-data-enconvert.md)
- [How to Ingest Audio Transcripts into Qdrant with EnConvert](https://www.enconvert.com/solutions/ingest-audio-transcripts-into-qdrant-enconvert-api.md)
- [How to Ingest a CSV into pgvector Without Losing Row Context](https://www.enconvert.com/solutions/ingest-csv-into-pgvector-with-row-context-enconvert-api.md)
- [How to Ingest CSV into CrewAI with EnConvert](https://www.enconvert.com/solutions/ingest-csv-into-crewai-with-enconvert.md)
