Metadata-Version: 2.4
Name: openfilings
Version: 0.1.0
Summary: Official Python client for the OpenFilings API — multi-market filings, canonical KPIs, and signed webhooks.
Project-URL: Homepage, https://openfilings.org
Project-URL: Documentation, https://openfilings.org/docs
Project-URL: Changelog, https://openfilings.org/docs#changelog
Project-URL: Repository, https://github.com/Openfilings/openfilings-python
Project-URL: Issues, https://github.com/Openfilings/openfilings-python/issues
Author-email: OpenFilings <contact@openfilings.org>
License: MIT
License-File: LICENSE
Keywords: edgar,filings,hedge-fund,kpi,openfilings,quant,sec,webhook,xbrl
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
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 :: Investment
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.100; extra == 'fastapi'
Provides-Extra: flask
Requires-Dist: flask>=2.3; extra == 'flask'
Description-Content-Type: text/markdown

# openfilings

[![PyPI](https://img.shields.io/pypi/v/openfilings.svg)](https://pypi.org/project/openfilings/)
[![CI](https://github.com/Openfilings/openfilings-python/actions/workflows/ci.yml/badge.svg)](https://github.com/Openfilings/openfilings-python/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Python](https://img.shields.io/pypi/pyversions/openfilings.svg)](https://pypi.org/project/openfilings/)

Official Python client for the [OpenFilings](https://openfilings.org) API — primary-source
regulatory filings, canonical cross-GAAP KPIs, and signed push webhooks across 16 markets
(US, EU/ESEF, UK, Japan, Hong Kong, South Korea, China, Taiwan, India, Singapore, Australia,
Canada, Brazil, Israel, UAE, Saudi Arabia).

```bash
pip install openfilings
```

## Quickstart

```python
from openfilings import Client

client = Client(api_key="of_...")  # or set OPENFILINGS_API_KEY

entity = client.entities.search(ticker="AAPL").entity
print(entity.canonical_key, [l.market_id for l in entity.listings])

filings = client.filings.list("AAPL", market_id="us", limit=5)
for f in filings.filings:
    print(f.form_type, f.filing_date, f.id)

financials = client.companies.financials(
    ticker="AAPL",
    market_id="us",
    fiscal_year_from=2020,
    fiscal_year_to=2024,
    line_items=["revenue", "ebitda", "gross_margin"],
)
for period in financials.periods:
    print(period.fiscal_year, period.values)
```

Get an API key from your dashboard: **My Page → API Keys** on
[openfilings.org](https://openfilings.org). Every response is a thin wrapper: documented fields
are plain attributes (`entity.canonical_key`), and `.raw` / `.to_dict()` always has the full JSON —
so a new field the API starts returning tomorrow is reachable today without an SDK upgrade.

## Async

`AsyncClient` mirrors `Client` one-to-one:

```python
import asyncio
from openfilings import AsyncClient

async def main() -> None:
    async with AsyncClient(api_key="of_...") as client:
        markets = await client.markets.list()
        print(markets.allowed_market_ids)

asyncio.run(main())
```

## Resources

| Resource | Purpose |
|---|---|
| `client.markets.list()` | Markets enabled on your plan — call first |
| `client.entities.search(...)` / `.lookup(...)` | Resolve a company by name, ticker, LEI, or ISIN |
| `client.filings.discover(...)` | Cross-market filing discovery, one call |
| `client.filings.list(ticker, ...)` | Filing history for one ticker/market |
| `client.filings.resolve(...)` | Resolve one specific filing |
| `client.filings.search(q, ...)` | Full-text search over filing section bodies |
| `client.filings.kpis(filing_id)` | Canonical, cross-GAAP KPIs for one filing |
| `client.filings.sections(filing_id)` / `.section(filing_id, key)` | Narrative sections (Item 1A, MD&A, …) |
| `client.companies.financials(...)` | Multi-year period series, no per-filing loop |
| `client.companies.earnings(market, ticker)` | Street upcoming/recent earnings |
| `client.companies.supply_chain(market, ticker)` | Named + anonymous supply-chain edges |
| `client.companies.kpi_taxonomy()` | Valid `line_items` codes |
| `client.notifications.list(...)` | Your alert inbox |
| `client.watchlist.list()` / `.add(...)` / `.update(...)` / `.remove(...)` | Followed tickers |
| `client.press.list_for_ticker(...)` / `.insider_transactions(...)` | Wire headlines + Form 4 (Pro+) |
| `client.webhooks.list()` / `.create(...)` / `.update(...)` / `.delete(...)` / `.test(...)` | Manage push endpoints (Business) |

## Webhooks

Business plan accounts can register a URL that receives a signed `POST` within seconds of a new
filing, press release, or insider transaction matching your watchlist:

```python
client.webhooks.create(
    "https://yourapp.example.com/openfilings-webhook",
    event_types=["filing.discovered"],
    secret="whsec_...",  # returned once — store it, later reads redact it
)
```

Verify deliveries with **zero extra dependencies** — `openfilings.webhooks` only needs the
standard library, so it works in a Lambda handler without the rest of the SDK installed.

**Recommended — FastAPI** (`pip install openfilings[fastapi]`):

```python
import os
from fastapi import Depends, FastAPI
from openfilings import Client
from openfilings.integrations.fastapi import openfilings_webhook_dependency
from openfilings.webhooks import WebhookEvent

app = FastAPI()
client = Client()  # OPENFILINGS_API_KEY
verify = openfilings_webhook_dependency(os.environ["OPENFILINGS_WEBHOOK_SECRET"])

@app.post("/openfilings-webhook")
async def on_filing(event: WebhookEvent = Depends(verify)) -> dict:
    if event.event == "filing.discovered":
        kpis = client.filings.kpis(event.data["filing_id"])
        # ... run your signal ...
    return {"ok": True}
```

Also Flask (`pip install openfilings[flask]`):

```python
from openfilings.integrations.flask import verify_flask_webhook
event = verify_flask_webhook(request, SECRET)
```

Or call `verify_webhook(raw_body, headers, secret)` directly with the stdlib helper.

See [`examples/fastapi_webhook.py`](examples/fastapi_webhook.py) and
[`examples/flask_webhook.py`](examples/flask_webhook.py) for complete handlers, and
[openfilings.org/docs/webhooks](https://openfilings.org/docs/webhooks) for the full event catalog,
payload envelope, and retry policy.

### Signature scheme

- `X-Webhook-Signature: sha256=<hex>`
- `X-Webhook-Timestamp: <ISO 8601 UTC>` — same value embedded in the JSON body's `timestamp`
- Signature = `HMAC-SHA256(secret, f"{timestamp}." + raw_body_bytes)`
- Envelope: `{"event": "...", "version": "1", "timestamp": "...", "data": {...}}`

Always verify against the **raw request body bytes** — re-serializing the parsed JSON before
verifying will break the signature if key order or whitespace differs.

## Error handling

```python
from openfilings import Client, QuotaExceededError, UpgradeRequiredError, NotFoundError

client = Client(api_key="of_...")
try:
    client.press.list_for_ticker("AAPL")
except UpgradeRequiredError as exc:
    print(f"needs {exc.min_plan} plan — feature: {exc.feature}")
except QuotaExceededError as exc:
    print(f"quota exceeded ({exc.dimension}); retry after {exc.retry_after}s")
except NotFoundError:
    print("not found")
```

All exceptions inherit from `openfilings.OpenFilingsError`. HTTP 429 with a short `Retry-After`
(≤ 5s) is retried automatically (2 attempts by default, tune with `max_retries=`); longer waits —
e.g. a daily quota reset — surface as `QuotaExceededError` instead of blocking your process.

## Configuration

| Environment variable | Purpose | Default |
|---|---|---|
| `OPENFILINGS_API_KEY` | `X-API-Key` sent on every request | — |
| `OPENFILINGS_BASE_URL` | API base URL | `https://api.openfilings.org` |

Both can be overridden per-client: `Client(api_key=..., base_url=...)`.

## Links

- [Documentation](https://openfilings.org/docs)
- [REST API reference](https://openfilings.org/docs/rest-api)
- [Webhooks guide](https://openfilings.org/docs/webhooks)
- [Pricing](https://openfilings.org/pricing)
- [Source (this repo is a read-only mirror of the SDK subtree in the main monorepo)](https://github.com/Openfilings/openfilings-python)
- [Issues / feature requests](https://github.com/Openfilings/openfilings-python/issues)

## License

MIT — see [LICENSE](LICENSE).
