# bypass-vuotlink-sdk

Python SDK for the bypass-vuotlink API — a service that resolves shortlink/safelink URLs
(e.g. vuotnhanh.com) to their final destination by running a real browser workflow.

## Install

```bash
pip install bypass-vuotlink-sdk
```

## Quick start

```python
from bypass_vuotlink_sdk import BypassVuotLink

# Sync
with BypassVuotLink(base_url="http://localhost:8000") as client:
    result = client.resolve("https://vuotnhanh.com/abc123")
    print(result.final_url)

# Async
from bypass_vuotlink_sdk import AsyncBypassVuotLink

async with AsyncBypassVuotLink(base_url="http://localhost:8000") as client:
    result = await client.resolve("https://vuotnhanh.com/abc123")
    print(result.final_url)
```

## Classes

### BypassVuotLink (sync)

```python
class BypassVuotLink:
    def __init__(
        self,
        base_url: str,
        *,
        timeout: float = 60.0,       # seconds
        api_key: str | None = None,  # sent as X-API-Key header
        extra_headers: dict[str, str] | None = None,
    ) -> None: ...

    @classmethod
    def from_config(cls, config: ClientConfig) -> BypassVuotLink: ...

    @classmethod
    def from_env(cls) -> BypassVuotLink:
        # reads: BYPASS_BASE_URL, BYPASS_TIMEOUT, BYPASS_API_KEY
        ...

    def resolve(self, url: str) -> ResolveResult: ...
    def close(self) -> None: ...

    # context manager
    def __enter__(self) -> BypassVuotLink: ...
    def __exit__(self, ...) -> None: ...
```

### AsyncBypassVuotLink (async)

Same interface as `BypassVuotLink` but all methods are async:

```python
class AsyncBypassVuotLink:
    def __init__(self, base_url: str, *, timeout: float = 60.0,
                 api_key: str | None = None,
                 extra_headers: dict[str, str] | None = None) -> None: ...

    @classmethod
    def from_config(cls, config: ClientConfig) -> AsyncBypassVuotLink: ...

    @classmethod
    def from_env(cls) -> AsyncBypassVuotLink: ...

    async def resolve(self, url: str) -> ResolveResult: ...
    async def close(self) -> None: ...

    # async context manager
    async def __aenter__(self) -> AsyncBypassVuotLink: ...
    async def __aexit__(self, ...) -> None: ...
```

### ClientConfig

```python
from dataclasses import dataclass
from typing import ClassVar

@dataclass
class ClientConfig:
    DEFAULT_TIMEOUT: ClassVar[float] = 60.0

    base_url: str
    timeout: float = 60.0
    api_key: str | None = None
    extra_headers: dict[str, str] = field(default_factory=dict)

    @classmethod
    def from_env(cls) -> ClientConfig:
        # BYPASS_BASE_URL  (required)
        # BYPASS_TIMEOUT   (optional, default 60.0)
        # BYPASS_API_KEY   (optional)
        ...

    def build_headers(self) -> dict[str, str]: ...
```

### ResolveResult

```python
@dataclass(frozen=True)
class ResolveResult:
    requested_url: str   # the URL you passed in
    final_url: str       # the resolved destination URL
    status_code: int | None
    title: str           # page title at final_url
    workflow: str        # which workflow handled it (e.g. "vuotnhanh")
```

## Exceptions

```
BypassVuotlinkError          # base — catch this to handle all SDK errors
├── UnsafeUrlError           # HTTP 400 — URL is private/unsafe
├── BrowserExecutionError    # HTTP 502 — browser workflow crashed
└── ApiError                 # any other unexpected HTTP status
      .status_code: int
      .detail: str
```

```python
from bypass_vuotlink_sdk import (
    BypassVuotLink,
    UnsafeUrlError,
    BrowserExecutionError,
    BypassVuotlinkError,
)

try:
    result = client.resolve(url)
except UnsafeUrlError:
    # URL points to a private/reserved address
    ...
except BrowserExecutionError:
    # the browser failed to process the page
    ...
except BypassVuotlinkError:
    # catch-all for any other SDK error
    ...
```

## Environment variable config

```bash
export BYPASS_BASE_URL="http://bypass-api:8000"
export BYPASS_TIMEOUT="90"
export BYPASS_API_KEY="secret"
```

```python
client = BypassVuotLink.from_env()
```

## API reference (server)

The SDK wraps a single endpoint:

```
POST /api/v1/browser
Content-Type: application/json

{ "url": "https://vuotnhanh.com/abc123" }
```

Response:

```json
{
  "requested_url": "https://vuotnhanh.com/abc123",
  "final_url": "https://example.com/destination",
  "status_code": 200,
  "title": "Example Domain",
  "workflow": "vuotnhanh"
}
```

Errors:

| Status | Meaning |
|--------|---------|
| 400    | URL is unsafe or private |
| 502    | Browser workflow execution failed |
