Official Python SDK for Firmaradar — enrichment platform for Norwegian company intelligence (KYC, AML, credit, ownership and risk).
Project description
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
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):
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:
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.
Quickstart
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:
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):
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):
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
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
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
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 file. © Firmaradar AS.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file firmaradar-0.1.0.tar.gz.
File metadata
- Download URL: firmaradar-0.1.0.tar.gz
- Upload date:
- Size: 33.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
44a4d5cd23c39ecf7b7ac8ffb49148b9d3d09d3c18594d2e59e10262453f7d21
|
|
| MD5 |
246687b1868fc16bbcadf65339dcf278
|
|
| BLAKE2b-256 |
aae10470bebab01e1a63452bf2928287157013e958d1ebf67aecd3dc31687526
|
File details
Details for the file firmaradar-0.1.0-py3-none-any.whl.
File metadata
- Download URL: firmaradar-0.1.0-py3-none-any.whl
- Upload date:
- Size: 28.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9eddedaafa05694119aa795c01282448e3ed43f12894404b07cdcc0ea1ab5d86
|
|
| MD5 |
d3784bb88c9fc3d7d9cdddd2c29627fd
|
|
| BLAKE2b-256 |
b7ef7d5eb3a58fab2947f7130795a005244a93e171444b9bfbc5a13dface5186
|