Metadata-Version: 2.4
Name: firmaradar
Version: 0.1.0
Summary: Official Python SDK for Firmaradar — enrichment platform for Norwegian company intelligence (KYC, AML, credit, ownership and risk).
Project-URL: Homepage, https://firmaradar.no
Project-URL: Documentation, https://firmaradar.no/dokumentasjon
Author-email: Firmaradar AS <lars@firmaradar.no>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: agents,aml,company,compliance,firmaradar,kyc,langchain,llamaindex,norway,ownership,risk
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
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
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.6
Provides-Extra: all
Requires-Dist: langchain-core>=0.2; extra == 'all'
Requires-Dist: llama-index-core>=0.10; extra == 'all'
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.2; extra == 'langchain'
Provides-Extra: llamaindex
Requires-Dist: llama-index-core>=0.10; extra == 'llamaindex'
Description-Content-Type: text/markdown

# Firmaradar Python SDK

Official Python SDK for **Firmaradar** — the enrichment platform for
Norwegian company intelligence. Firmaradar fuses data from multiple
authoritative sources (Brønnøysundregistrene, Skatteetaten, foreign
PEP/sanctions registers, public-grants registries) and adds proprietary
enrichment on top — so a single call returns a decision-ready view for
KYC, AML, credit, ownership and risk workflows, not a raw registry
record.

The SDK is a thin, fully typed client over the Firmaradar REST API
(`httpx` + Pydantic v2), with sync **and** async variants and optional
LangChain / LlamaIndex tool wrappers for AI-agent stacks.

## Installation

```bash
pip install firmaradar                # core SDK
pip install 'firmaradar[langchain]'   # + LangChain tool wrappers
pip install 'firmaradar[llamaindex]'  # + LlamaIndex tool spec
pip install 'firmaradar[all]'         # everything
```

From source (this repo):

```bash
pip install ./sdk/python              # or: pip install -e './sdk/python[all]'
```

Requires Python 3.10+.

## Authentication

The SDK authenticates with a Firmaradar API key, sent as the `X-API-Key`
header. Pass it explicitly or set the `FIRMARADAR_API_KEY` environment
variable:

```python
from firmaradar import Firmaradar

fr = Firmaradar(api_key="fr_...")   # or: export FIRMARADAR_API_KEY=fr_...
```

API keys are managed on your Firmaradar account at
[firmaradar.no](https://firmaradar.no).

## Quickstart

```python
from firmaradar import Firmaradar

fr = Firmaradar()  # reads FIRMARADAR_API_KEY

# Find a company, then pull its decision-ready profile
page = fr.companies.search("Equinor")
orgnr = page.items[0].orgnr

company = fr.companies.get(orgnr, fields=["group", "owners", "grants"])
print(company.navn, "-", company.summary)

# Ownership tree towards ultimate beneficial owners
tree = fr.companies.ownership(orgnr, direction="up", depth=5)
for owner in tree.owners:
    print(owner.navn, owner.eierandel_prosent)

# Transparent risk score with component breakdown
score = fr.risk.score(orgnr)
print(score.score, score.level, score.components)
```

### Async

Every operation has an async twin. Either construct `AsyncFirmaradar`
directly, or derive an async session from an existing client:

```python
import asyncio
from firmaradar import Firmaradar

fr = Firmaradar()

async def screen(orgnrs: list[str]):
    async with fr.async_session() as session:
        return await asyncio.gather(*(session.companies.get(o) for o in orgnrs))

companies = asyncio.run(screen(["923609016", "914594685"]))
```

### Async AML reports (submit → poll)

AML screening requires a signed DPA with Firmaradar. Calling
`start_report` confirms the screening and records the purpose in the
audit trail (60-month retention per Hvitvaskingsloven §35):

```python
job = fr.aml.start_report("923609016", purpose="kyc_onboarding")

status = fr.aml.get_report(job.rapport_id)
while status.status in ("pending", "running"):
    time.sleep(5)
    status = fr.aml.get_report(job.rapport_id)

if status.status == "done":
    print(status.score, status.level, status.pdf_url)
```

## Operations

| Namespace | Method | What it returns |
|---|---|---|
| `companies` | `search(q, nace, kommune, limit, cursor)` | Paginated company search |
| `companies` | `get(orgnr, fields, owners, ...)` | Full enriched company profile |
| `companies` | `roles(orgnr, ...)` | BRREG roles (board, CEO, signature, auditor) |
| `companies` | `ownership(orgnr, direction, depth, ...)` | Ownership tree (down / up-UBO / both) |
| `companies` | `financials(orgnr, years, regnskapstype)` | Financial history per accounting year |
| `companies` | `announcements(orgnr)` | BRREG announcements (normalized categories) |
| `risk` | `score(orgnr)` | Risk score 0-100 + level + components |
| `risk` | `check_fiv(orgnr)` | Foretak-i-vanskeligheter (NUES a-e) assessment |
| `aml` | `start_report(orgnr, purpose)` | Start async AML report (DPA required) |
| `aml` | `get_report(report_id)` | Poll AML report status/result |
| `monitoring` | `add(orgnr, ip_alerts, categories)` | Add company to monitoring |
| `monitoring` | `remove(orgnr)` | Remove company from monitoring (idempotent) |
| `monitoring` | `list()` | List monitored companies |

All responses are typed Pydantic models (`firmaradar.models`). Models
tolerate additive API changes — unknown fields are kept, never fatal.

## Error handling

Non-2xx responses raise typed exceptions mapped from the API's error
contract (HTTP status + stable `error_code`):

```python
from firmaradar import (
    AuthenticationError,     # 401 — bad/expired API key
    PermissionDeniedError,   # 403 — not authorized / compliance gate / DPA missing
    NotFoundError,           # 404 — unknown orgnr / report id
    ConflictError,           # 409 — e.g. company already monitored
    ValidationError,         # 400/422 — malformed parameters
    QuotaExceededError,      # 402/429 — quota or rate limit (see .retry_after_s)
    ServiceUnavailableError, # 5xx — transient; retry later
    APIConnectionError,      # network unreachable (APITimeoutError for timeouts)
)

try:
    fr.monitoring.add("923609016")
except ConflictError:
    pass  # already monitored — fine
except QuotaExceededError as exc:
    retry_in = exc.retry_after_s or 60
except PermissionDeniedError as exc:
    print(exc.status_code, exc.error_code, exc)  # e.g. 403 EXTENSION_NOT_ACTIVE
```

Malformed organisation numbers are rejected client-side (`ValueError`)
before they cost an API call; `"923 609 016"`-style formatting is
normalized automatically.

## LangChain integration

```python
from firmaradar.langchain import get_tools          # requires [langchain]
from langchain_anthropic import ChatAnthropic
from langchain.agents import create_tool_calling_agent

tools = get_tools(api_key="fr_...")   # list[StructuredTool], sync + async
agent = create_tool_calling_agent(ChatAnthropic(model="claude-sonnet-4-5"), tools, prompt)
```

## LlamaIndex integration

```python
from firmaradar.llamaindex import get_tool_spec     # requires [llamaindex]
from llama_index.core.agent import ReActAgent

agent = ReActAgent.from_tools(get_tool_spec(api_key="fr_...").to_tool_list())
```

Tool names, descriptions and argument schemas are shared between both
integrations and mirror the Firmaradar MCP server — the same operations
behave identically whether an agent reaches them over MCP, LangChain or
LlamaIndex.

## Configuration

| Setting | Argument | Environment variable | Default |
|---|---|---|---|
| API key | `api_key=` | `FIRMARADAR_API_KEY` | — (required) |
| Base URL | `base_url=` | `FIRMARADAR_BASE_URL` | `https://firmaradar.no` |
| Timeout (s) | `timeout=` | `FIRMARADAR_TIMEOUT_S` | `30` |

## Development

```bash
python3 -m pytest sdk/python/tests/ -q   # no network, no DB — httpx MockTransport
ruff check sdk/python
```

## License

Apache License 2.0 — see the [LICENSE](LICENSE) file.
© Firmaradar AS.
