Skip to main content

Official Python SDK for the Yutori API

Project description

Yutori Python SDK & CLI

PyPI version Python 3.9+

The official Python library and CLI for the Yutori API.

Yutori provides APIs for building web agents that autonomously execute tasks on the web. The SDK offers both synchronous and asynchronous clients with full type annotations, plus a CLI for authentication and managing resources from the terminal.

Documentation

Installation

pip install yutori

Getting Started

Authentication

The easiest way to authenticate is to run this in your terminal:

Terminal:

yutori auth login

This opens your browser to log in with your Yutori account and saves an API key to ~/.yutori/config.json. The SDK and CLI automatically use this saved key.

Alternatively, you can set the YUTORI_API_KEY environment variable, or pass the key directly:

from yutori import YutoriClient

# Uses saved credentials from `yutori auth login`, or YUTORI_API_KEY env var
client = YutoriClient()

# Or pass explicitly
client = YutoriClient(api_key="yt-...")

API key resolution order: explicit parameter > YUTORI_API_KEY env var > ~/.yutori/config.json.

API Overview

The Yutori API provides four main capabilities:

API Description SDK Namespace
n1 Pixels-to-actions LLM for browser control client.chat
Browsing One-time browser automation tasks client.browsing
Research Deep web research using 100+ tools client.research
Scouting Continuous web monitoring on a schedule client.scouts

n1 API

The n1 API is a pixels-to-actions LLM that processes screenshots and predicts browser actions (click, type, scroll, etc.). It follows the OpenAI Chat Completions interface:

response = client.chat.completions.create(
    model="n1-latest",
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "Describe the screenshot and search for Yutori."
                },
                {
                    "type": "image_url",
                    "image_url": {
                        "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Google_homepage_%28as_of_January_2024%29.jpg/1280px-Google_homepage_%28as_of_January_2024%29.jpg"
                    }
                }
            ]
        }
    ]
)

# Get the thoughts
message = response.choices[0].message
print(message.content)

# Get the tool calls, such as browser interaction actions
if message.tool_calls:
    for tool_call in message.tool_calls:
        print(f"Action: {tool_call.function.name}")
        print(f"Arguments: {tool_call.function.arguments}")

For screenshot-heavy agent loops, the SDK also provides opt-in trimming helpers under yutori.n1:

from yutori.n1 import estimate_messages_size_bytes, trimmed_messages_to_fit

if estimate_messages_size_bytes(messages) > 9_500_000:
    messages, size_bytes, removed = trimmed_messages_to_fit(
        messages,
        max_bytes=9_500_000,
        keep_recent=6,
    )

response = await client.chat.completions.create(
    model="n1-latest",
    messages=messages,
)

This keeps the raw OpenAI-compatible client.chat.completions.create(...) call unchanged, while giving Yutori users a safer message-preparation helper for large screenshot histories. In long-lived loops, assign the trimmed copy back to your owned history before the next step so old screenshots do not keep accumulating in memory. The size pre-check is there to avoid deep-copying the full history on every step when trimming is not needed.

If you don't want to manage your own browser infrastructure, use the Browsing API which calls n1 on a cloud browser.

Browsing API

Run one-time browser automation tasks. An AI agent operates a cloud browser to complete your task.

# Create a browsing task
task = client.browsing.create(
    task="Give me a list of all employees (names and titles) of Yutori.",
    start_url="https://yutori.com",
)

# Poll for completion
import time
while True:
    result = client.browsing.get(task["task_id"])
    if result["status"] in ("succeeded", "failed"):
        break
    time.sleep(5)

print(result)

Structured Output with Webhooks

You can define the output structure using a JSON schema dict or a Pydantic BaseModel class (Pydantic is optional):

from pydantic import BaseModel  # optional dependency

class Employee(BaseModel):
    name: str
    title: str

task = client.browsing.create(
    task="Give me a list of all employees (names and titles) of Yutori.",
    start_url="https://yutori.com",
    max_steps=75,
    webhook_url="https://example.com/webhook",
    output_schema=Employee,  # auto-converted to JSON schema
)
Using a JSON schema dict instead
task = client.browsing.create(
    task="Give me a list of all employees (names and titles) of Yutori.",
    start_url="https://yutori.com",
    max_steps=75,
    webhook_url="https://example.com/webhook",
    output_schema={
        "type": "array",
        "items": {
            "type": "object",
            "properties": {
                "name": {"type": "string"},
                "title": {"type": "string"}
            }
        }
    }
)

Research API

Perform deep web research using 100+ MCP tools including search engines, APIs, and data sources.

task = client.research.create(
    query="What are the latest developments in quantum computing from the past week?",
    user_timezone="America/Los_Angeles",
)

# Poll for results
import time
while True:
    result = client.research.get(task["task_id"])
    if result["status"] in ("succeeded", "failed"):
        break
    time.sleep(5)

print(result)

Structured Output

from pydantic import BaseModel  # optional dependency

class Finding(BaseModel):
    title: str
    summary: str
    source_url: str

task = client.research.create(
    query="What are the latest developments in quantum computing?",
    user_timezone="America/Los_Angeles",
    webhook_url="https://example.com/webhook",
    output_schema=Finding,  # auto-converted to JSON schema
)
Using a JSON schema dict instead
task = client.research.create(
    query="What are the latest developments in quantum computing?",
    user_timezone="America/Los_Angeles",
    webhook_url="https://example.com/webhook",
    output_schema={
        "type": "array",
        "items": {
            "type": "object",
            "properties": {
                "title": {"type": "string"},
                "summary": {"type": "string"},
                "source_url": {"type": "string"}
            }
        }
    }
)

Scouting API

Scouts run on a configurable schedule to monitor the web and send notifications when relevant updates occur.

from yutori import YutoriClient

client = YutoriClient(api_key="yt-...")

# Create a scout that monitors for updates
scout = client.scouts.create(
    query="Tell me about the latest news, product updates, and announcements about Yutori AI",
)
print(f"Created scout: {scout['id']}")

# List all active scouts
scouts = client.scouts.list(status="active")

# Get a specific scout
scout = client.scouts.get("scout_abc123")

# Pause a scout
client.scouts.update("scout_abc123", status="paused")

# Resume a scout
client.scouts.update("scout_abc123", status="active")

# Archive a scout
client.scouts.update("scout_abc123", status="done")

# Get scout updates
updates = client.scouts.get_updates("scout_abc123", limit=20)

# Delete a scout
client.scouts.delete("scout_abc123")

Structured Output with Webhooks

from pydantic import BaseModel  # optional dependency

class NewsItem(BaseModel):
    headline: str
    summary: str
    source_url: str

scout = client.scouts.create(
    query="Tell me about the latest news, product updates, and announcements about Yutori AI",
    output_interval=86400,  # Daily
    user_timezone="America/Los_Angeles",
    skip_email=True,
    webhook_url="https://example.com/webhook",
    output_schema=NewsItem,  # auto-converted to JSON schema
)
Using a JSON schema dict instead
scout = client.scouts.create(
    query="Tell me about the latest news, product updates, and announcements about Yutori AI",
    output_interval=86400,  # Daily
    user_timezone="America/Los_Angeles",
    skip_email=True,
    webhook_url="https://example.com/webhook",
    output_schema={
        "type": "array",
        "items": {
            "type": "object",
            "properties": {
                "headline": {"type": "string"},
                "summary": {"type": "string"},
                "source_url": {"type": "string"}
            }
        }
    }
)

Async Usage

The SDK provides an async client with the same interface:

import asyncio
from yutori import AsyncYutoriClient

async def main():
    async with AsyncYutoriClient(api_key="yt-...") as client:
        # All methods are async
        usage = await client.get_usage()
        print(usage)

        scouts = await client.scouts.list()
        print(scouts)

        scout = await client.scouts.create(
            query="Monitor https://example.com for updates",
            output_interval=3600,
        )
        print(scout)

asyncio.run(main())

Error Handling

The SDK raises typed exceptions for API errors:

from yutori import YutoriClient, APIError, AuthenticationError

try:
    client = YutoriClient(api_key="invalid-key")
    client.get_usage()
except AuthenticationError as e:
    print(f"Invalid API key: {e}")
except APIError as e:
    print(f"API error (status {e.status_code}): {e.message}")

Exception Types

Exception Status Code Description
AuthenticationError 401, 403 Invalid or missing API key
APIError 4xx, 5xx General API error with status code

Configuration

from yutori import YutoriClient

client = YutoriClient(
    api_key="yt-...",                          # Or: yutori auth login / YUTORI_API_KEY
    base_url="https://api.yutori.com/v1",      # Default
    timeout=30.0,                               # Request timeout in seconds
)

CLI

The CLI provides commands for authentication and managing Yutori resources from the terminal.

# Version
yutori --version

# Authentication
yutori auth login       # Log in via browser
yutori auth status      # Show current auth status
yutori auth logout      # Remove saved credentials

# Scouts
yutori scouts list                          # List your scouts
yutori scouts get SCOUT_ID                  # Get scout details
yutori scouts create -q "monitor for news"  # Create a scout
yutori scouts delete SCOUT_ID               # Delete a scout

# Browsing
yutori browse run "extract all prices" https://example.com/products
yutori browse run "log in and continue" https://example.com/login --require-auth
yutori browse get TASK_ID

# Research
yutori research run "latest developments in quantum computing"
yutori research get TASK_ID

# Usage
yutori usage            # Show API usage statistics

Run yutori --help or yutori <command> --help for full option details.

Requirements

Examples

See examples/ for complete working examples, including a browser automation agent using the n1 API.

Contributing

See CONTRIBUTING.md for development setup and guidelines.

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

yutori-0.4.3.tar.gz (46.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

yutori-0.4.3-py3-none-any.whl (43.0 kB view details)

Uploaded Python 3

File details

Details for the file yutori-0.4.3.tar.gz.

File metadata

  • Download URL: yutori-0.4.3.tar.gz
  • Upload date:
  • Size: 46.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for yutori-0.4.3.tar.gz
Algorithm Hash digest
SHA256 c639e60c67fd2f00c5612e2bca6614ca559a4eb8af3be44323e72965a0fcc383
MD5 a3b3e13c57ed30026e4e1ca93dca68ea
BLAKE2b-256 744ff28f98351b4d1ca1437cc57b1863eeca49a613faa2194c7b57bef4a17167

See more details on using hashes here.

File details

Details for the file yutori-0.4.3-py3-none-any.whl.

File metadata

  • Download URL: yutori-0.4.3-py3-none-any.whl
  • Upload date:
  • Size: 43.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for yutori-0.4.3-py3-none-any.whl
Algorithm Hash digest
SHA256 0744eb1cef447005cef9f884fc2f24c1ef8c7cbb0aded9f7ba6f4ac04d78908c
MD5 68ed9249cdda85e2aaad74563f8724cc
BLAKE2b-256 2581fa3951f7571e27c8d539ec252dd955deb7fa20f131426c08b32b4ba2d6ae

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page