firmaradar
v0.1.0
Published
Official TypeScript SDK for Firmaradar — enrichment platform for Norwegian company intelligence (KYC, AML, credit, ownership and risk).
Maintainers
Readme
Firmaradar TypeScript SDK
Official TypeScript/JavaScript 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,
built on the platform-native fetch API with zero runtime
dependencies — it runs in Node.js (≥ 18.17), browsers, Deno, Bun,
Cloudflare Workers and Vercel Edge. Optional LangChain.js / Vercel AI SDK
tool wrappers plug the same operations into AI-agent stacks.
Installation
npm install firmaradar # core SDK
npm install firmaradar @langchain/core # + LangChain.js tool wrappers
npm install firmaradar ai # + Vercel AI SDK tool wrappersFrom source (this repo):
npm install ./sdk/typescriptRequires Node.js 18.17+ (or any runtime with WHATWG fetch).
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:
import { Firmaradar } from "firmaradar";
const fr = new Firmaradar({ apiKey: "fr_..." }); // or: export FIRMARADAR_API_KEY=fr_...API keys are managed on your Firmaradar account at firmaradar.no.
Quickstart
import { Firmaradar } from "firmaradar";
const fr = new Firmaradar(); // reads FIRMARADAR_API_KEY
console.log((await fr.companies.get("923609016")).navn);Going deeper:
// Find a company, then pull its decision-ready profile
const page = await fr.companies.search("Equinor");
const orgnr = page.items[0].orgnr;
const company = await fr.companies.get(orgnr, { fields: ["group", "owners", "grants"] });
console.log(company.navn, "-", company.summary);
// Ownership tree towards ultimate beneficial owners
const tree = await fr.companies.ownership(orgnr, { direction: "up", depth: 5 });
for (const owner of tree.owners) console.log(owner.navn, owner.eierandel_prosent);
// Transparent risk score with component breakdown
const score = await fr.risk.score(orgnr);
console.log(score.score, score.level, score.components);Every operation returns a Promise — batch with Promise.all:
const companies = await Promise.all(
["923609016", "914594685"].map((orgnr) => fr.companies.get(orgnr)),
);Async AML reports (submit → poll)
AML screening requires a signed DPA with Firmaradar. Calling
startReport confirms the screening and records the purpose in the
audit trail (60-month retention per Hvitvaskingsloven §35):
const job = await fr.aml.startReport("923609016", { purpose: "kyc_onboarding" });
let status = await fr.aml.getReport(job.rapport_id);
while (status.status === "pending" || status.status === "running") {
await new Promise((resolve) => setTimeout(resolve, 5000));
status = await fr.aml.getReport(job.rapport_id);
}
if (status.status === "done") console.log(status.score, status.level, status.pdf_url);Operations
| Namespace | Method | What it returns |
|---|---|---|
| companies | search(q?, options?) | Paginated company search |
| companies | get(orgnr, options?) | Full enriched company profile |
| companies | roles(orgnr, options?) | BRREG roles (board, CEO, signature, auditor) |
| companies | ownership(orgnr, options?) | Ownership tree (down / up-UBO / both) |
| companies | financials(orgnr, options?) | Financial history per accounting year |
| companies | announcements(orgnr) | BRREG announcements (normalized categories) |
| risk | score(orgnr) | Risk score 0-100 + level + components |
| risk | checkFiv(orgnr) | Foretak-i-vanskeligheter (NUES a-e) assessment |
| aml | startReport(orgnr, options?) | Start async AML report (DPA required) |
| aml | getReport(reportId) | Poll AML report status/result |
| monitoring | add(orgnr, options?) | Add company to monitoring |
| monitoring | remove(orgnr) | Remove company from monitoring (idempotent) |
| monitoring | list() | List monitored companies |
All responses are typed (Company, RiskScore, AmlReportStatus, ...)
with wire-format field names — the same names you see on every other
Firmaradar surface (REST, MCP, n8n, Make, Power Automate). Responses
tolerate additive API changes: unknown fields are kept, never fatal.
Error handling
Non-2xx responses throw typed errors mapped from the API's error
contract (HTTP status + stable error_code):
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 .retryAfterS)
ServiceUnavailableError, // 5xx — transient; retry later
APIConnectionError, // network unreachable (APITimeoutError for timeouts)
} from "firmaradar";
try {
await fr.monitoring.add("923609016");
} catch (err) {
if (err instanceof ConflictError) {
// already monitored — fine
} else if (err instanceof QuotaExceededError) {
const retryInS = err.retryAfterS ?? 60;
} else if (err instanceof PermissionDeniedError) {
console.log(err.statusCode, err.errorCode, err.message); // e.g. 403 EXTENSION_NOT_ACTIVE
} else {
throw err;
}
}Malformed organisation numbers are rejected client-side (RangeError)
before they cost an API call; "923 609 016"-style formatting is
normalized automatically.
LangChain.js integration
Requires the optional peer dependency @langchain/core (≥ 0.3.44):
import { getTools } from "firmaradar/langchain";
const tools = getTools({ apiKey: "fr_..." }); // 13 structured tools
// -> pass to createAgent / bindTools / createToolCallingAgent / ...Vercel AI SDK integration
Requires the optional peer dependency ai (v5 or newer):
import { generateText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { getTools } from "firmaradar/ai";
const result = await generateText({
model: anthropic("claude-sonnet-4-5"),
tools: getTools({ apiKey: "fr_..." }),
prompt: "Gjør due diligence på orgnr 923609016",
});Tool names, descriptions and argument schemas are shared between both
integrations and mirror the Firmaradar Python SDK and the Firmaradar MCP
server — the same operations behave identically whether an agent reaches
them over MCP, LangChain or the AI SDK. The framework-agnostic catalog
(TOOL_SPECS, invokeTool, executeTool) is exported from the core
entry point, so any other agent framework can be wired up without extra
dependencies.
Note: the AI SDK's own type definitions reference the
json-schemapackage without shipping its types. If you compile withskipLibCheck: false, addnpm install -D @types/json-schema(this applies to everyaiconsumer, not just this SDK).
Configuration
| Setting | Option | Environment variable | Default |
|---|---|---|---|
| API key | apiKey | FIRMARADAR_API_KEY | — (required) |
| Base URL | baseUrl | FIRMARADAR_BASE_URL | https://firmaradar.no |
| Timeout | timeoutMs (milliseconds) | FIRMARADAR_TIMEOUT_S (seconds) | 30 s |
| Transport | fetch | — | global fetch |
The environment variables are shared with the Firmaradar Python SDK, so one configuration serves both.
Development
cd sdk/typescript
npm install
npm test # vitest — no network, no API key (mocked fetch)
npm run typecheck # tsc --strict
npm run build # dual ESM (dist/esm) + CJS (dist/cjs) via tscFrom the repo root: npm --prefix sdk/typescript test.
License
Apache-2.0 — © Firmaradar AS.
