Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.1.0"
".": "0.2.0"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 26
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browser-use%2Fbrowser-use-3a9488448292a0736b08b2d6427e702eaf7106d86345ca2e65b64bed4398b036.yml
openapi_spec_hash: 5ff2781dcc11a0c9aa353f5cb14bcc16
config_hash: 9d52be5177b2ede4cb0633c04f4cc4ef
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browser-use%2Fbrowser-use-ce018db4d6891d645cfb220c86d17ac1d431e1ba2f604e8015876b17a5a11149.yml
openapi_spec_hash: e9a00924682ab214ca5d8b6b5c84430e
config_hash: dd3e22b635fa0eb9a7c741a8aaca2a7f
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## 0.2.0 (2025-08-19)

Full Changelog: [v0.1.0...v0.2.0](https://github.com/browser-use/browser-use-python/compare/v0.1.0...v0.2.0)

### Features

* **api:** manual updates ([6266282](https://github.com/browser-use/browser-use-python/commit/6266282a615344fdab0737d29adc9124a3bf8b8d))
* **api:** manual updates ([2d9ba52](https://github.com/browser-use/browser-use-python/commit/2d9ba52b23e53c581360afc655fa8d665a106814))
* Improve Docs ([6e79b7c](https://github.com/browser-use/browser-use-python/commit/6e79b7c5cfc7cf54f1474521025fa713f200bc3b))

## 0.1.0 (2025-08-18)

Full Changelog: [v0.0.2...v0.1.0](https://github.com/browser-use/browser-use-python/compare/v0.0.2...v0.1.0)
Expand Down
42 changes: 29 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ client = BrowserUse(
api_key=os.environ.get("BROWSER_USE_API_KEY"), # This is the default and can be omitted
)

me = client.users.me.retrieve()
print(me.additional_credits_balance_usd)
task = client.tasks.create(
task="Search for the top 10 Hacker News posts and return the title and url.",
)
print(task.id)
```

While you can provide an `api_key` keyword argument,
Expand All @@ -56,8 +58,10 @@ client = AsyncBrowserUse(


async def main() -> None:
me = await client.users.me.retrieve()
print(me.additional_credits_balance_usd)
task = await client.tasks.create(
task="Search for the top 10 Hacker News posts and return the title and url.",
)
print(task.id)


asyncio.run(main())
Expand Down Expand Up @@ -89,8 +93,10 @@ async def main() -> None:
api_key="My API Key",
http_client=DefaultAioHttpClient(),
) as client:
me = await client.users.me.retrieve()
print(me.additional_credits_balance_usd)
task = await client.tasks.create(
task="Search for the top 10 Hacker News posts and return the title and url.",
)
print(task.id)


asyncio.run(main())
Expand Down Expand Up @@ -137,7 +143,9 @@ from browser_use_sdk import BrowserUse
client = BrowserUse()

try:
client.users.me.retrieve()
client.tasks.create(
task="Search for the top 10 Hacker News posts and return the title and url.",
)
except browser_use_sdk.APIConnectionError as e:
print("The server could not be reached")
print(e.__cause__) # an underlying Exception, likely raised within httpx.
Expand Down Expand Up @@ -180,7 +188,9 @@ client = BrowserUse(
)

# Or, configure per-request:
client.with_options(max_retries=5).users.me.retrieve()
client.with_options(max_retries=5).tasks.create(
task="Search for the top 10 Hacker News posts and return the title and url.",
)
```

### Timeouts
Expand All @@ -203,7 +213,9 @@ client = BrowserUse(
)

# Override per-request:
client.with_options(timeout=5.0).users.me.retrieve()
client.with_options(timeout=5.0).tasks.create(
task="Search for the top 10 Hacker News posts and return the title and url.",
)
```

On timeout, an `APITimeoutError` is thrown.
Expand Down Expand Up @@ -244,11 +256,13 @@ The "raw" Response object can be accessed by prefixing `.with_raw_response.` to
from browser_use_sdk import BrowserUse

client = BrowserUse()
response = client.users.me.with_raw_response.retrieve()
response = client.tasks.with_raw_response.create(
task="Search for the top 10 Hacker News posts and return the title and url.",
)
print(response.headers.get('X-My-Header'))

me = response.parse() # get the object that `users.me.retrieve()` would have returned
print(me.additional_credits_balance_usd)
task = response.parse() # get the object that `tasks.create()` would have returned
print(task.id)
```

These methods return an [`APIResponse`](https://github.com/browser-use/browser-use-python/tree/main/src/browser_use_sdk/_response.py) object.
Expand All @@ -262,7 +276,9 @@ The above interface eagerly reads the full response body when you make the reque
To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods.

```python
with client.users.me.with_streaming_response.retrieve() as response:
with client.tasks.with_streaming_response.create(
task="Search for the top 10 Hacker News posts and return the title and url.",
) as response:
print(response.headers.get("X-My-Header"))

for line in response.iter_lines():
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "browser-use-sdk"
version = "0.1.0"
version = "0.2.0"
description = "The official Python library for the browser-use API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion src/browser_use_sdk/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "browser_use_sdk"
__version__ = "0.1.0" # x-release-please-version
__version__ = "0.2.0" # x-release-please-version
22 changes: 10 additions & 12 deletions src/browser_use_sdk/resources/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@ def create(
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
) -> TaskCreateResponse:
"""
Create and start a new AI agent task.
Create and start a new Browser Use Agent task.

This is the main endpoint for running AI agents. You can either:

1. Start a new session with a new task
2. Add a follow-up task to an existing session
1. Start a new session with a new task.
2. Add a follow-up task to an existing session.

When starting a new session:

Expand All @@ -95,9 +95,8 @@ def create(
- Browser profiles: Control browser settings and environment (only used for new
sessions)
- File uploads: Include documents for the agent to work with
- Structured output: Define the format you want results in
- Task metadata: Add custom data for tracking and organization (useful when
using webhooks)
- Structured output: Define the format of the task result
- Task metadata: Add custom data for tracking and organization

Args:

Expand Down Expand Up @@ -564,12 +563,12 @@ async def create(
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
) -> TaskCreateResponse:
"""
Create and start a new AI agent task.
Create and start a new Browser Use Agent task.

This is the main endpoint for running AI agents. You can either:

1. Start a new session with a new task
2. Add a follow-up task to an existing session
1. Start a new session with a new task.
2. Add a follow-up task to an existing session.

When starting a new session:

Expand All @@ -590,9 +589,8 @@ async def create(
- Browser profiles: Control browser settings and environment (only used for new
sessions)
- File uploads: Include documents for the agent to work with
- Structured output: Define the format you want results in
- Task metadata: Add custom data for tracking and organization (useful when
using webhooks)
- Structured output: Define the format of the task result
- Task metadata: Add custom data for tracking and organization

Args:

Expand Down
44 changes: 24 additions & 20 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -722,20 +722,20 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str
@mock.patch("browser_use_sdk._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: BrowserUse) -> None:
respx_mock.get("/users/me").mock(side_effect=httpx.TimeoutException("Test timeout error"))
respx_mock.post("/tasks").mock(side_effect=httpx.TimeoutException("Test timeout error"))

with pytest.raises(APITimeoutError):
client.users.me.with_streaming_response.retrieve().__enter__()
client.tasks.with_streaming_response.create(task="x").__enter__()

assert _get_open_connections(self.client) == 0

@mock.patch("browser_use_sdk._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: BrowserUse) -> None:
respx_mock.get("/users/me").mock(return_value=httpx.Response(500))
respx_mock.post("/tasks").mock(return_value=httpx.Response(500))

with pytest.raises(APIStatusError):
client.users.me.with_streaming_response.retrieve().__enter__()
client.tasks.with_streaming_response.create(task="x").__enter__()
assert _get_open_connections(self.client) == 0

@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
Expand All @@ -762,9 +762,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
return httpx.Response(500)
return httpx.Response(200)

respx_mock.get("/users/me").mock(side_effect=retry_handler)
respx_mock.post("/tasks").mock(side_effect=retry_handler)

response = client.users.me.with_raw_response.retrieve()
response = client.tasks.with_raw_response.create(task="x")

assert response.retries_taken == failures_before_success
assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success
Expand All @@ -786,9 +786,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
return httpx.Response(500)
return httpx.Response(200)

respx_mock.get("/users/me").mock(side_effect=retry_handler)
respx_mock.post("/tasks").mock(side_effect=retry_handler)

response = client.users.me.with_raw_response.retrieve(extra_headers={"x-stainless-retry-count": Omit()})
response = client.tasks.with_raw_response.create(task="x", extra_headers={"x-stainless-retry-count": Omit()})

assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0

Expand All @@ -809,9 +809,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
return httpx.Response(500)
return httpx.Response(200)

respx_mock.get("/users/me").mock(side_effect=retry_handler)
respx_mock.post("/tasks").mock(side_effect=retry_handler)

response = client.users.me.with_raw_response.retrieve(extra_headers={"x-stainless-retry-count": "42"})
response = client.tasks.with_raw_response.create(task="x", extra_headers={"x-stainless-retry-count": "42"})

assert response.http_request.headers.get("x-stainless-retry-count") == "42"

Expand Down Expand Up @@ -1539,10 +1539,10 @@ async def test_parse_retry_after_header(self, remaining_retries: int, retry_afte
async def test_retrying_timeout_errors_doesnt_leak(
self, respx_mock: MockRouter, async_client: AsyncBrowserUse
) -> None:
respx_mock.get("/users/me").mock(side_effect=httpx.TimeoutException("Test timeout error"))
respx_mock.post("/tasks").mock(side_effect=httpx.TimeoutException("Test timeout error"))

with pytest.raises(APITimeoutError):
await async_client.users.me.with_streaming_response.retrieve().__aenter__()
await async_client.tasks.with_streaming_response.create(task="x").__aenter__()

assert _get_open_connections(self.client) == 0

Expand All @@ -1551,10 +1551,10 @@ async def test_retrying_timeout_errors_doesnt_leak(
async def test_retrying_status_errors_doesnt_leak(
self, respx_mock: MockRouter, async_client: AsyncBrowserUse
) -> None:
respx_mock.get("/users/me").mock(return_value=httpx.Response(500))
respx_mock.post("/tasks").mock(return_value=httpx.Response(500))

with pytest.raises(APIStatusError):
await async_client.users.me.with_streaming_response.retrieve().__aenter__()
await async_client.tasks.with_streaming_response.create(task="x").__aenter__()
assert _get_open_connections(self.client) == 0

@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
Expand Down Expand Up @@ -1582,9 +1582,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
return httpx.Response(500)
return httpx.Response(200)

respx_mock.get("/users/me").mock(side_effect=retry_handler)
respx_mock.post("/tasks").mock(side_effect=retry_handler)

response = await client.users.me.with_raw_response.retrieve()
response = await client.tasks.with_raw_response.create(task="x")

assert response.retries_taken == failures_before_success
assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success
Expand All @@ -1607,9 +1607,11 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
return httpx.Response(500)
return httpx.Response(200)

respx_mock.get("/users/me").mock(side_effect=retry_handler)
respx_mock.post("/tasks").mock(side_effect=retry_handler)

response = await client.users.me.with_raw_response.retrieve(extra_headers={"x-stainless-retry-count": Omit()})
response = await client.tasks.with_raw_response.create(
task="x", extra_headers={"x-stainless-retry-count": Omit()}
)

assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0

Expand All @@ -1631,9 +1633,11 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
return httpx.Response(500)
return httpx.Response(200)

respx_mock.get("/users/me").mock(side_effect=retry_handler)
respx_mock.post("/tasks").mock(side_effect=retry_handler)

response = await client.users.me.with_raw_response.retrieve(extra_headers={"x-stainless-retry-count": "42"})
response = await client.tasks.with_raw_response.create(
task="x", extra_headers={"x-stainless-retry-count": "42"}
)

assert response.http_request.headers.get("x-stainless-retry-count") == "42"

Expand Down