Metadata-Version: 2.4
Name: linkshieldai
Version: 0.3.1
Summary: Python SDK for the LinkShieldAI URL safety API.
Project-URL: Homepage, https://linkshieldai.com
Project-URL: Documentation, https://docs.linkshieldai.com
Author: LinkShieldAI
License: MIT
Keywords: linkshieldai,phishing,sdk,security,url-safety
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.27.0
Provides-Extra: dev
Requires-Dist: build>=1.2.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: twine>=5.0.0; extra == 'dev'
Description-Content-Type: text/markdown

# LinkShieldAI Python SDK

Python wrapper for the LinkShieldAI API at `https://api.linkshieldai.com`.

The SDK supports:

- URL risk scanning with `POST /v1/scan`
- Three scan depths: `standard`, `detailed`, `deep`
- Optional model analysis and raw signal output
- Screenshot download
- NSFW site checks
- Chimera AI classification
- Sync and async clients
- Retry/backoff for transient API failures
- A small command-line tool

## Install

```bash
pip install linkshieldai
```

## Authentication

The API uses Bearer authentication. The key is sent in the `Authorization`
header and never in the query string.

```python
from linkshieldai import LinkShieldAI

client = LinkShieldAI(api_key="YOUR_API_KEY")
```

Or set an environment variable:

```powershell
$env:LINKSHIELDAI_API_KEY = "YOUR_API_KEY"
```

```python
from linkshieldai import LinkShieldAI

client = LinkShieldAI()
```

## Scan a URL

Wraps:

```text
POST https://api.linkshieldai.com/v1/scan
```

```python
from linkshieldai import LinkShieldAI

with LinkShieldAI(api_key="YOUR_API_KEY") as client:
    result = client.scan("https://example.com", mode="standard")

    print(result.verdict)        # SAFE, MALICIOUS or UNKNOWN
    print(result.request_id)
    print(result.reason_codes)

    if result.is_malicious:
        print("Block or review this URL")
```

### Verdicts

`verdict` is `SAFE`, `MALICIOUS` or `UNKNOWN`.

`UNKNOWN` means no decisive signal was available. **It does not mean safe**, and
`is_safe` is `False` for it:

| verdict | `is_malicious` | `is_safe` | `is_unknown` |
| --- | --- | --- | --- |
| `MALICIOUS` | `True` | `False` | `False` |
| `SAFE` | `False` | `True` | `False` |
| `UNKNOWN` | `False` | `False` | `True` |

### Modes

| mode | What it adds |
| --- | --- |
| `standard` | Fast reputation and threat-feed decision. |
| `detailed` | Redirect, page-preview, brand, and screenshot signals when available. |
| `deep` | Page fingerprinting when earlier signals are inconclusive. |

### Model analysis

Pass `ai=True` to add model analysis when page fingerprinting is inconclusive.
It is off by default and applies to `deep` only, since the model scores the page
HTML that only `deep` fetches. Without it, `risk_score` and `confidence` stay
`None`.

```python
result = client.scan("https://example.com", mode="deep", ai=True)
print(result.verdict, result.risk_score, result.confidence)
```

### Raw signals

`reason_codes` tells you why a verdict was reached. `include_signals=True` also
returns what each source independently reported, so you can apply your own
precedence:

```python
result = client.scan("https://example.com", include_signals=True)

if result.signals:
    print(result.signals.url_reputation)      # malicious | safe | unknown
    print(result.signals.domain_reputation)
    print(result.signals.threat_feed)
    print(result.signals.external_reputation) # bool
    print(result.signals.degraded)            # a lookup failed
```

### Result fields

```python
result.verdict            # SAFE | MALICIOUS | UNKNOWN
result.request_id         # quote this in support tickets
result.mode
result.confidence         # None unless ai=True
result.risk_score         # None unless ai=True
result.threat_categories
result.reason_codes
result.brand_target       # detected impersonation target, if any
result.screenshot_url
result.submitted_url
result.normalized_url
result.redirects
result.scanned_at
result.freshness
result.engine_version
result.signals            # None unless include_signals=True
result.raw                # the untouched JSON payload
```

## Async

```python
import asyncio
from linkshieldai import AsyncLinkShieldAI

async def main():
    async with AsyncLinkShieldAI(api_key="YOUR_API_KEY") as client:
        result = await client.scan("https://example.com", mode="deep", ai=True)
        print(result.verdict)

asyncio.run(main())
```

## Other endpoints

```python
nsfw = client.nsfw_check("https://example.com")
print(nsfw.is_nsfw)

chimera = client.chimera("https://google.com")
print(chimera.result, chimera.probability)

image_bytes = client.get_screenshot("05046f.png")
client.get_screenshot("https://api.linkshieldai.com/screenshot/05046f.png", "site.png")
```

## Command line

```bash
linkshieldai --api-key YOUR_API_KEY scan https://example.com
linkshieldai --api-key YOUR_API_KEY scan https://example.com --mode detailed
linkshieldai --api-key YOUR_API_KEY scan https://example.com --mode deep --ai
linkshieldai --api-key YOUR_API_KEY scan https://example.com --include-signals
linkshieldai --api-key YOUR_API_KEY nsfw https://example.com
linkshieldai --api-key YOUR_API_KEY chimera https://google.com
linkshieldai --api-key YOUR_API_KEY screenshot 05046f.png --output site.png
```

Omit `--api-key` when `LINKSHIELDAI_API_KEY` is set.

## Custom API host

```python
client = LinkShieldAI(api_key="YOUR_API_KEY", base_url="https://api.linkshieldai.com")
```

## Timeouts and retries

Defaults are `timeout=10.0`, `max_retries=2`, `backoff_factor=0.5`.

Retries apply to temporary connection failures and HTTP `429`, `502`, `503` and
`504`. `Retry-After` is honoured when the API sends it.

```python
client = LinkShieldAI(api_key="YOUR_API_KEY", timeout=15.0, max_retries=3)
```

## Errors

```python
from linkshieldai import (
    APIConnectionError,
    APIResponseError,
    APIStatusError,
    AuthenticationError,
    RateLimitError,
)
```

| Error | Raised when |
| --- | --- |
| `AuthenticationError` | No API key was provided. |
| `RateLimitError` | HTTP 429. Carries `retry_after` when the API sends it. |
| `APIStatusError` | Any other non-success status. Carries `status_code`. |
| `APIResponseError` | Malformed JSON, or a payload containing an error. |
| `APIConnectionError` | Timeouts, DNS failures, connection failures. |

## Upgrading from 0.2.x / 0.3.0

`basic_check()` and `detailed_check()` have been removed. Use `scan()`:

```python
# before
result = client.basic_check(url)
if result.is_malicious: ...

# after
result = client.scan(url)
if result.is_malicious: ...
```

```python
# before
result = client.detailed_check(url)
print(result.screenshot_url, result.tag)

# after
result = client.scan(url, mode="detailed")
print(result.screenshot_url, result.brand_target)
```

The CLI commands `basic` and `detailed` are replaced by `scan --mode`.

The underlying `GET /` and `GET /classify_link` endpoints still work and are not
being removed without notice, so existing direct HTTP integrations are
unaffected.

## Documentation

<https://docs.linkshieldai.com>
