Metadata-Version: 2.5
Name: filingstudio-proxy
Version: 0.2.0
Summary: Server-side proxy for Filing Studio: hold the API key on your FastAPI server; the browser talks only to your route.
Project-URL: Homepage, https://filingstudio.com
Project-URL: Documentation, https://filingstudio.com/docs#sdk
Author-email: Filing Studio <support@filingstudio.com>
License: MIT
License-File: LICENSE
Keywords: citations,edgar,fastapi,filings,finance,provenance,proxy,sec
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: fastapi>=0.100
Requires-Dist: httpx>=0.24
Provides-Extra: test
Requires-Dist: pytest>=7; extra == 'test'
Description-Content-Type: text/markdown

# filingstudio-proxy

Server-side proxy for [Filing Studio](https://filingstudio.com) on FastAPI.
Your API key stays on your server; the browser talks only to your route.
This is the Python twin of `@filingstudio/proxy` (Next.js and Express), with
the same contract, so `@filingstudio/react` works unchanged in front of it.

```bash
pip install filingstudio-proxy
```

```python
import os
from fastapi import FastAPI
from filingstudio_proxy import filing_studio_router

app = FastAPI()
app.include_router(
    filing_studio_router(api_key=os.environ["FILING_STUDIO_API_KEY"]),
    prefix="/api/filings",
)
```

Then in the browser: `<ProvenanceProvider proxy="/api/filings">`.

## Public demo? Cap each visitor

```python
filing_studio_router(api_key=key, budget=40)   # 40 metered requests per visitor per day
```

Over budget the route answers `429` with an honest note the drawer shows
("Daily demo limit reached… This says nothing about what the filings
contain"), never a fake "not found". Coverage checks are never metered, and
cached answers are served before the meter, so reopening a receipt is free.

Meter on your own login instead of the visitor IP:

```python
filing_studio_router(
    api_key=key,
    budget=100,
    visitor_key=lambda req: req.state.user_id,
    limit_for=lambda req: req.state.daily_limit,   # optional per-user quota
)
```

## What it guarantees

- Only `GET` and `POST`, only to an allowlist of `/v1` read paths. Dot
  segments and backslashes are refused before allowlisting.
- Nothing from the browser request is forwarded except the `/v1` path, its
  query string, and a POST body (capped at 64 KB).
- Successful `GET`s are cached in-process (one hour by default) and served
  before the meter. Errors are never cached.
- The reply carries `X-FS-Demo-Remaining` when metered; the React SDK's
  `onLimit` reads it.
- A missing key answers `503`; an unreachable upstream answers `502`. Neither
  message can contain the key.

## Options

| option | default | meaning |
|---|---|---|
| `api_key` | required | your `fsk_…` key, server-side only |
| `base_url` | `https://api.filingstudio.com` | upstream base |
| `allow` | search, trace, traces, verify, coverage, filings, tables | allowed `/v1` prefixes |
| `cache_ttl_s` | `3600` | GET cache TTL; `0` disables |
| `budget` | `None` | per-visitor daily cap (int) or a shared `Budget` |
| `visitor_key` | client IP, cookie fallback | who a request is charged to |
| `limit_for` | `None` | per-request quota override |
| `timeout_s` | `30` | upstream timeout |
| `transport` | `None` | an `httpx` transport, for tests |

## Develop

```bash
pip install -e .[test]
pytest
```

MIT
