One typed TypeScript interface for Norwegian public data.
Norway Open Data SDK provides a consistent, runtime-validated client for official Norwegian APIs from Brønnøysundregistrene, SSB, FHI, Kartverket, Entur, MET Norway, Data.norge, Norges Bank, Stortinget, Statens vegvesen and NVE. It also wraps the documented third-party public electricity API from Hva koster strømmen?.
Requests go directly from your Node.js application to the source API. The SDK has no hosted backend, database, account system or scraping layer.
Requires Node.js 22 or newer. TypeScript declarations are included.
Version 0.3.0 is the current release. Install it with:
npm install norway-open-data-sdk
Contributors can also build and test a local tarball:
git clone https://github.com/iamkm1/Norway-Open-Data.git
cd Norway-Open-Data
corepack pnpm install
corepack pnpm build
corepack pnpm pack
Install the generated tarball from another project:
npm install /path/to/Norway-Open-Data/norway-open-data-sdk-0.3.0.tgz
import { NorwayOpenData, type Company, type NorwegianAddress } from "norway-open-data-sdk";
const norway = new NorwayOpenData({
applicationName: "my-application",
contactEmail: "developer@example.no",
});
const company = await norway.companies.get("923609016"); // OpenDataResponse<Company>
console.log(company.data.name);
const addresses = await norway.addresses.search({
query: "Haraldsgata 100, Haugesund",
limit: 1,
});
console.log(addresses.data.items[0]); // NorwegianAddress
const profile = await norway.profiles.company("923609016");
console.log(profile.data.location);
Result types follow each provider's own domain language rather than one shared prefix: companies
return Company, Kartverket lookups return NorwegianAddress, and every entity type is exported
from the package root, so editor auto-import finds the exact name.
Anonymous methods work without configuration. Entur and NVDB require applicationName; MET
requires applicationName and contactEmail; NVE HydAPI requires the caller's own API key.
| Source | Namespace | Capabilities | Access |
|---|---|---|---|
| Brønnøysundregistrene | companies |
Organizations, search and sub-entities | Anonymous |
| Statistics Norway / SSB | statistics |
PxWeb metadata and JSON-stat2 data | Anonymous |
| FHI | health |
Health-register statistics with preserved suppression flags | Anonymous |
| Kartverket | addresses, places |
Addresses, place names and nearby search | Anonymous |
| Entur | transport |
Autocomplete, departures and journeys | Identification required |
| MET Norway | weather |
Locationforecast data and current entry | Identification + contact email |
| Data.norge | catalog |
Datasets, data services and publishers | Anonymous |
| Norges Bank | currency |
Exchange rates, policy rate and NOWA | Anonymous |
| Stortinget | parliament |
Representatives, parties, cases, votes, questions and meetings | Anonymous |
| Statens vegvesen / NVDB | roads |
Road metadata, objects and network segments | Identification required |
| NVE | energy, hazards |
Energy data, warnings and hydrology | Anonymous; API key for HydAPI |
| Hva koster strømmen? | electricity |
Hourly spot prices for all five bidding zones | Anonymous third-party API |
profiles composes several providers into one answer: company combines Brønnøysundregistrene
with a Kartverket address match, and address combines Kartverket, MET Norway, NVE and NVDB.
Hva koster strømmen? is an independent third-party service, not a government or official data provider. Its documentation says the API derives EUR electricity prices from ENTSO-E and converts them to NOK using the latest Norges Bank exchange rate.
See the complete capability matrix for every namespace, method, access requirement and known limitation.
The following examples use the configured norway client from the quick start.
const company = await norway.companies.get("923609016");
console.log(company.data.name);
const results = await norway.catalog.search({
query: "public transport",
type: ["dataset"],
size: 5,
});
console.log(results.data.items);
const profile = await norway.profiles.company("923609016");
console.log(profile.data.location);
One call answers a location from four providers at once:
const place = await norway.profiles.address("Haraldsgata 100, Haugesund");
console.log(place.data.address.municipalityName); // Kartverket
console.log(place.data.weather?.temperature); // MET Norway
console.log(place.data.hazards); // Exact administrative-area matches from NVE
console.log(place.data.hazardMatches); // Code/name evidence for each match
console.log(place.data.roads); // First-page NVDB bounding-box candidates
console.log(place.data.roadSearch); // Bounds, page size and truncation state
// components is an array with one entry per SDK operation:
for (const component of place.data.components) {
console.log(component.operation, component.status);
// "addresses.search" "available"
// "hazards.getFloodWarnings" "available"
// "weather.current" "omitted" (reason: "not-configured")
}
Enrichment degrades gracefully: weather and roads are omitted when the client has no
applicationName/contactEmail, rather than failing the whole call. Optional provider failures
degrade the same way: if MET, NVDB or one Varsom warning feed errors at request time, the profile
still returns with every surviving section, and the failing operation is reported as omitted with
reason provider-error and a sanitized error name and message. Caller cancellation is never
degraded — aborting the request still rejects the whole call. components reports each operation
as available, with its source, retrievedAt and cached values, or omitted, with a
not-configured, missing-coordinate, not-applicable or provider-error reason. A component's retrievedAt is
when that SDK operation resolved, including cache hits, not when its payload was originally fetched
upstream. Sources include provider attribution text, with service-specific wording for each Varsom
warning feed.
roads contains only the first provider page intersecting the WGS84 box in roadSearch; it is not
a geometry-distance result. The default box extends approximately 250 metres from the address to
each side. Check roadSearch.truncated before assuming that all candidates were returned.
Safety: automatic warning discovery checks an explicit municipality by exact code, then exact case-insensitive, Unicode-normalized name. It considers a county only when the warning publishes no municipalities, because a county can be parent context. Forecast-region names are not matched automatically. An empty match is never an all-clear. For any safety decision, consult the current, complete official warnings directly from Varsom/NVE and follow their guidance.
FHI publishes statistics from Norwegian health registers — cause-of-death rates, abortion statistics, drug sales and municipal public-health indicators — through one open API:
const sources = await norway.health.getSources(); // daar, abr, nokkel, ...
const result = await norway.health.query({
source: "daar",
tableId: 754,
selections: {
DAAR: ["2020"],
KJONN: ["Total"],
HJERTEKAR: ["Total"],
MEASURE_TYPE: ["RATE_NO"],
},
});
console.log(result.data.rows[0]?.value); // age-standardized cardiovascular death rate
Discover what a table accepts with health.getTables(source), health.getTableMetadata(source, tableId) and health.getTableDimensions(source, tableId); a selection of ["*"] selects every
category of a dimension, including nested child categories.
Small-count health cells are suppressed at the source — anonymized or not computable. The SDK
preserves FHI's markers instead of hiding them: a suppressed row is { value: null, flag: ":" },
and result.data.flags maps every symbol to the provider's own explanation. A flag is
information, not absence: keep flagged observations suppressed in anything you build on top.
This namespace uses the third-party Hva koster strømmen? API. The provider documents the values as ENTSO-E electricity prices in EUR converted to NOK with a Norges Bank exchange rate.
const prices = await norway.electricity.getPrices({ area: "NO1" });
console.log(prices.data[0]);
const now = await norway.electricity.getCurrentPrice({ area: "NO5" });
console.log(now.data?.nokPerKwh);
getPrices() returns one entry per elapsed hour in the requested Europe/Oslo calendar day: normally
24, but 23 or 25 across daylight-saving transitions. Normalized endsAt values follow the next
chronological startsAt (or the following local midnight). Use { includeRaw: true } when you also
need the provider-native timestamps.
List endpoints expose auto-paginating async iterators that request each page on demand:
for await (const company of norway.companies.searchAll({ municipalityCode: "1106" })) {
console.log(company.name);
}
Bound the walk with maxItems or maxPages:
const iterator = norway.catalog.searchAll({ query: "transport" }, { maxItems: 50 });
maxItems must be a non-negative integer. maxPages must be an integer from 1 to 100 and defaults
to 100. NVDB iterators treat continuation markers as opaque and throw ResponseValidationError
before requesting an already-seen marker when a provider repeats a cursor or returns a cycle.
Available on companies.searchAll, catalog.searchAll, parliament.searchCasesAll,
roads.searchRoadObjectsAll and roads.getRoadNetworkAll.
const controller = new AbortController();
await norway.weather.forecast(
{
latitude: 59.4138,
longitude: 5.268,
},
{
signal: controller.signal,
},
);
const rate = await norway.currency.getExchangeRate(
{
from: "EUR",
to: "NOK",
},
{
includeRaw: true,
},
);
console.log(rate.raw);
Note that the normalized result names the pair with SDMX terminology: the from currency is
data.baseCurrency and the to currency is data.quoteCurrency, alongside date, value and
an optional unit multiplier for currencies quoted per 100 units.
import { NorwayOpenData } from "norway-open-data-sdk";
const nveApiKey = process.env.NVE_HYDAPI_KEY;
const norway = new NorwayOpenData({
applicationName: "my-company-my-application",
contactEmail: "developer@example.no",
timeoutMs: 10_000,
retries: 2,
cache: { enabled: true, maxEntries: 250 },
fetch: globalThis.fetch,
...(nveApiKey === undefined ? {} : { credentials: { nve: { apiKey: nveApiKey } } }),
});
| Option | Default | Purpose |
|---|---|---|
applicationName |
None | Caller identity required by Entur, MET and NVDB |
contactEmail |
None | Contact address required by MET |
timeoutMs |
10_000 |
Per-attempt timeout in milliseconds |
retries |
2 |
Retry attempts after the initial request; range 0–10 |
fetch |
globalThis.fetch |
Custom Fetch-compatible implementation |
cache.enabled |
false |
Enables the per-client memory cache |
cache.ttlMs |
Provider default | Overrides provider-specific TTLs |
cache.maxEntries |
100 |
Maximum memory-cache entries |
credentials.nve.apiKey |
None | Free NVE HydAPI key for stations and observations |
When required by the selected service, each application must provide its own identity, contact
address and credentials. The SDK contains no shared email, API key or fallback identity. Missing
required values raise ConfigurationError before a request is made.
Every provider method accepts optional signal, includeRaw and bypassCache request options.
Every successful operation returns OpenDataResponse<T>:
type OpenDataResponse<T> = {
data: T;
source: {
id: string;
name: string;
homepage: string;
documentation: string;
license?: string;
attribution?: string;
};
retrievedAt: string;
cached: boolean;
raw?: unknown;
};
data is the typed result; source identifies the provider and includes its licence or attribution
when declared; retrievedAt is an ISO 8601 timestamp; cached reports a memory-cache hit; and
raw is included only when requested. Raw payloads remain runtime-validated and may be allowlisted
or sanitized.
import {
NorwayOpenData,
NotFoundError,
OpenDataError,
ProviderError,
RateLimitError,
} from "norway-open-data-sdk";
const norway = new NorwayOpenData();
try {
await norway.companies.get("000000000");
} catch (error) {
if (error instanceof NotFoundError) {
console.error("Organization not found");
} else if (error instanceof RateLimitError) {
console.error("Retry after seconds:", error.retryAfter);
} else if (error instanceof OpenDataError) {
console.error(error.provider, error.statusCode, error.message);
} else {
throw error;
}
}
Exported errors are OpenDataError, ConfigurationError, InputValidationError,
NotFoundError, RateLimitError, ProviderError, RequestTimeoutError and
ResponseValidationError. Retryable provider responses, timeouts and temporary network failures
use bounded retries and honor Retry-After; validation and other client errors are not retried.
Caller cancellation also surfaces as ProviderError, with the abort reason attached as cause.
When retry or reporting logic must distinguish a deliberate abort from a provider failure, check
the caller's own signal rather than the error:
try {
await norway.companies.get("923609016", { signal: controller.signal });
} catch (error) {
if (controller.signal.aborted) {
// Cancelled by this application — do not retry or alert.
} else if (error instanceof ProviderError) {
// Genuine provider failure.
}
}
const norway = new NorwayOpenData({
cache: { enabled: true, maxEntries: 250 },
});
const first = await norway.companies.get("923609016");
console.log(first.cached); // false
const second = await norway.companies.get("923609016");
console.log(second.cached); // true
norway.clearCache();
const afterClear = await norway.companies.get("923609016");
console.log(afterClear.cached); // false
Caching is disabled by default. When enabled, each client uses a bounded in-memory TTL/LRU cache
with provider-specific TTLs. Failures are never cached, and { bypassCache: true } skips both
reads and writes. clearCache() removes all entries shared by that NorwayOpenData instance. See
Architecture for implementation details.
flowchart LR
App[Developer application]
Facade[NorwayOpenData facade]
Adapters[Provider adapters]
Core[HTTP, validation, retry and cache]
APIs[Public-sector APIs and documented third-party API]
App --> Facade
Facade --> Adapters
Adapters --> Core
Core --> APIs
The facade creates all service namespaces with one shared configuration and cache.
Provider adapters own request construction, runtime schemas and safe normalization.
The shared core handles timeout, cancellation, retries, errors and caching.
Provider-specific semantics are preserved instead of forced into one universal model.
Generate the TypeDoc API reference locally with:
pnpm run docs
TypeDoc writes to docs/api; open docs/api/index.html. The Pages workflow can deploy the same
reference when GitHub Pages is enabled.
pnpm test
pnpm test:coverage
Live tests are opt-in representative contract probes against the supported public-sector APIs and the third-party electricity endpoint:
NORWAY_OPEN_DATA_APPLICATION_NAME=my-application \
NORWAY_OPEN_DATA_CONTACT_EMAIL=developer@example.no \
pnpm test:live
PowerShell:
$env:NORWAY_OPEN_DATA_APPLICATION_NAME = "my-application"
$env:NORWAY_OPEN_DATA_CONTACT_EMAIL = "developer@example.no"
pnpm test:live
See Testing for live-test coverage, smoke tests and CI behavior.
Development requires Node.js 22+ and pnpm 10:
pnpm install
pnpm verify
Read CONTRIBUTING.md before opening a pull request. User-visible changes require a Changeset:
pnpm changeset
The SDK source code is available under the MIT Licence. MIT does not automatically apply to returned data: every provider or dataset retains its own terms, licence, traffic limits and attribution requirements. Users must follow those terms when using or redistributing data.
Hva koster strømmen? is an independent third-party service. Its API page describes ENTSO-E as the electricity-data source and Norges Bank as the exchange-rate source; see PROVIDERS.md for the documented lineage and attribution notes.
Norway Open Data SDK is an independent open-source project. It is not affiliated with, sponsored by or endorsed by Norwegian public authorities.
Personal and restricted data are outside the project scope.
roadSearch for the exact bounds and truncation state.See PROVIDERS.md for provider-specific limits, licences and caveats.