# Docs

Veldora serves Norwegian company data, public tenders, addresses, laws and exchange rates, and Finnish company data, over HTTP and MCP. You pay per request in USDC on Base with x402. Base URL: `https://veldora.no`.

Payments are in USDC on Base mainnet (`eip155:8453`). You don't need an account or an API key, and we don't charge for failed calls.

Dataset pages: [Norwegian companies](https://veldora.no/data/norwegian-companies) · [Norwegian public tenders](https://veldora.no/data/norwegian-tenders) · [Norwegian public contract awards](https://veldora.no/data/contract-awards) · [Tender alerts](https://veldora.no/data/tender-alerts) · [Norwegian addresses](https://veldora.no/data/norwegian-addresses) · [Norwegian laws and regulations](https://veldora.no/data/norwegian-laws) · [NOK exchange rates](https://veldora.no/data/nok-exchange-rates)

- [Quickstart](#quickstart)
- [Payment](#payment)
- [Endpoints](#endpoints)
- [Errors](#errors)
- [Retries](#retries)
- [Rate limits](#rate-limits)
- [Data notes](#data)
- [MCP](#mcp)
- [Versioning and deprecation](#versioning)
- [Markdown for agents](#markdown)
- [Licences](#licences)
- [Network](#network)

## Quickstart

1. Create a wallet and put a small amount of USDC on Base in it. You do not need ETH for gas. Use a dedicated wallet, not your main one.
2. Install the client: `npm install @x402/fetch @x402/evm viem`
3. Wrap `fetch` and call an endpoint.

```
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";

const signer = privateKeyToAccount(process.env.BUYER_PRIVATE_KEY);
const payFetch = wrapFetchWithPaymentFromConfig(fetch, {
  schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(signer) }],
});

const res = await payFetch("https://veldora.no/v1/companies/923609016");
const { data, meta } = await res.json();
```

Free routes need no wallet: `GET /v1/health`, `GET /openapi.json`, and listing tools at `POST /mcp`.

## Authentication by payment

There are no accounts and no API keys. The payment is the credential. The flow follows x402 v2.

1. Send the request without payment. Veldora replies `402`. The `PAYMENT-REQUIRED` header holds base64-encoded JSON with the requirements. The body has the same JSON.
2. Pick the entry in `accepts`: scheme `exact`, network `eip155:8453`, the USDC contract, the exact amount in atomic units (6 decimals) and the recipient.
3. Sign an EIP-3009 `transferWithAuthorization` for that amount. The facilitator submits it on chain and pays the gas.
4. Repeat the same request with the base64 payload in the `PAYMENT-SIGNATURE` header.
5. On 200 you get the data and a `PAYMENT-RESPONSE` header with the settlement receipt, including the transaction hash.

```
curl -i https://veldora.no/v1/companies/923609016

HTTP/2 402
content-type: application/json
payment-required: eyJ4NDAyVmVyc2lvbiI6Mi...

{"x402Version":2,"resource":{"url":"https://veldora.no/v1/companies/923609016", ...},
 "accepts":[{"scheme":"exact","network":"eip155:8453","amount":"5000","asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913","payTo":"0x...", ...}]}
```

Rules that protect you:

- We reject invalid input with 400 before asking for payment.
- We never charge for a response with status 400 or higher, and we release data only after the payment settles.
- Payment must match exactly: network, asset, recipient and amount. We reject overpayment as `amount_mismatch` too.

## Endpoints

All paid endpoints are `GET` and return JSON. Successful responses use one envelope:

```
{
  "data": { ... },
  "page": { "page": 0, "size": 20, "total": 1 },
  "meta": {
    "source": "brreg",
    "fetched_at": "2026-10-12T08:15:00Z",
    "stale": false,
    "license": "NLOD-2.0",
    "attribution": "Contains data under the Norwegian licence for Open Government data (NLOD) distributed by Brønnøysundregistrene."
  }
}
```

`page` appears on list endpoints only. We reject unknown query parameters with 400 `unknown_parameter`. Organisation numbers are 9 digits with a valid mod-11 check digit.

## Look up a company $0.005

GET /v1/companies/{orgnr}

One legal entity from Enhetsregisteret, cleaned.

| Parameter | Type | Description |
|---|---|---|
| `orgnr` (path) | string, 9 digits | Organisation number, mod-11 checked. |

```
{
  "data": {
    "orgnr": "923609016",
    "name": "EQUINOR ASA",
    "name_masked": false,
    "org_form": { "code": "ASA", "description": "Allmennaksjeselskap" },
    "status": "active",
    "bankrupt": false,
    "bankrupt_date": null,
    "winding_up": false,
    "winding_up_date": null,
    "forced_dissolution": false,
    "deleted_date": null,
    "founded": "1972-09-18",
    "registered_date": "1995-02-09",
    "vat_registered": true,
    "in_business_register": true,
    "industry": [{ "code": "06.100", "description": "Utvinning av råolje" }],
    "employees": 4000,
    "sector_code": "2100",
    "address": {
      "street": "Forusbeen 50",
      "postal_code": "4035",
      "city": "STAVANGER",
      "municipality": "STAVANGER",
      "municipality_number": "1103",
      "country_code": "NO"
    },
    "postal_address": null,
    "website": "www.equinor.com",
    "parent_orgnr": null,
    "last_annual_accounts_year": 2025,
    "links": { "brreg": "https://virksomhet.brreg.no/nb/oppslag/enheter/923609016" }
  },
  "meta": { "source": "brreg", "fetched_at": "2026-10-12T08:15:00Z", "stale": false, "license": "NLOD-2.0", "attribution": "..." }
}
```

Status is one of `active`, `bankrupt`, `winding_up`, `forced_dissolution`, `deleted`. If several apply, the first of deleted, bankrupt, forced dissolution, winding up wins. The values above are an illustration of the shape.

## Search companies $0.01

GET /v1/companies

Search by name, municipality, industry, legal form and status. Name search leaves out sole proprietorships (ENK).

| Parameter | Type | Description |
|---|---|---|
| `q` | string, 2 to 100 chars | Name, or part of it. |
| `municipality` | string, 4 digits | Municipality number, e.g. `0301` for Oslo. |
| `nace` | string | NACE code or prefix, e.g. `62` or `62.010`. |
| `org_form` | string, 2 to 5 capitals | Legal form: AS, ASA, ENK, NUF, SA, STI and so on. |
| `status` | enum | `active` (default), `bankrupt`, `winding_up`, `any`. |
| `page` | integer, 0 or more | Zero-based page. Default 0. |
| `size` | integer, 1 to 100 | Results per page. Default 20. |

```
{
  "data": [
    { "orgnr": "923609016", "name": "EQUINOR ASA", "status": "active", "org_form": { "code": "ASA", "description": "Allmennaksjeselskap" }, ... }
  ],
  "page": { "page": 0, "size": 20, "total": 1 },
  "meta": { "source": "brreg", "fetched_at": "2026-10-12T08:15:00Z", "stale": false, "license": "NLOD-2.0", "attribution": "..." }
}
```

Each item has the same fields as the company lookup. `page * size` may not pass 10 000. See [data notes](#data).

## Tenders published by a company $0.01

GET /v1/companies/{orgnr}/tenders

Tenders where this organisation is the buyer, linked by organisation number.

| Parameter | Type | Description |
|---|---|---|
| `orgnr` (path) | string, 9 digits | Buyer organisation number. |
| `open` | boolean | Only tenders whose deadline has not passed. Default `false`. |
| `page` | integer, 0 or more | Default 0. |
| `size` | integer, 1 to 100 | Default 20. |

The response has the same shape as tender search below, with `meta.source` set to `ted` or `ted+doffin`.

## Contracts won by a company $0.01

GET /v1/companies/{orgnr}/awards

Public contracts this organisation has won, from Norwegian contract award notices on TED and Doffin in the last 180 days. Newest first.

| Parameter | Type | Description |
|---|---|---|
| `orgnr` (path) | string, 9 digits | Winner organisation number. |
| `page` | integer, 0 or more | Default 0. |
| `size` | integer, 1 to 100 | Default 20. |

Each item is a tender record (same shape as tender search) with an `award` object:

```
"award": {
  "winners": [{ "orgnr": "990432368", "name": "TWODAY AS" }],
  "other_winners": 0,
  "value": { "amount": 9402000, "currency": "NOK" },
  "decided": "2026-09-30",
  "concluded": "2026-10-01"
}
```

Winner names come from Brønnøysundregistrene. Sole proprietorships stay unnamed (`name: null`). `other_winners` counts winners without a Norwegian organisation number. Award notices also carry `award` in tender search and in tenders by buyer; other notices have `award: null`.

## Search public tenders $0.01

GET /v1/tenders

Norwegian public procurement notices from Doffin and TED. Notices published in both appear once, with links to both. Ids are `doffin:…` or `ted:…`.

| Parameter | Type | Description |
|---|---|---|
| `q` | string, 2 to 100 chars | Words in title or description. |
| `cpv` | string | CPV code or prefix. Up to 10, comma-separated. `72` matches all IT services. |
| `region` | string | NUTS code (`NO`, `NO08`, `NO081`) or 2-digit county number. |
| `deadline_from` | date | Deadline on or after this date, `YYYY-MM-DD`. |
| `deadline_to` | date | Deadline on or before this date. |
| `buyer_orgnr` | string, 9 digits | Only tenders from this buyer. |
| `open` | boolean | Only tenders whose deadline has not passed. Default `true`. |
| `page` | integer, 0 or more | Default 0. |
| `size` | integer, 1 to 100 | Default 20. |

```
{
  "data": [
    {
      "id": "ted:123456-2026",
      "source": "ted",
      "publication_number": "123456-2026",
      "title": "Rammeavtale for IT-konsulenttjenester",
      "description": "...",
      "buyer": { "name": "Statens vegvesen", "orgnr": "971032081", "country": "NO", "city": "Oslo" },
      "cpv": ["72000000"],
      "main_cpv": "72000000",
      "regions": ["NO081"],
      "notice_type": "cn-standard",
      "procedure_type": "open",
      "contract_nature": "services",
      "published": "2026-10-01",
      "deadline": "2026-11-01T12:00:00+01:00",
      "estimated_value": { "amount": 5000000, "currency": "NOK" },
      "links": { "ted": "https://ted.europa.eu/en/notice/-/detail/123456-2026" }
    }
  ],
  "page": { "page": 0, "size": 20, "total": 1 },
  "meta": { "source": "ted", "fetched_at": "2026-10-12T04:00:00Z", "stale": false, "as_of": "2026-10-12", "license": "TED reuse terms (Commission Decision 2011/833/EU)", "attribution": "..." }
}
```

The example values are illustrative. `buyer.orgnr` is `null` when the notice has no valid Norwegian number. `estimated_value` is `null` when the notice gives none. `description` is cut at 2000 characters.

## Get one tender $0.005

GET /v1/tenders/{id}

One notice with buyer, CPV codes, regions, deadline and the link to Doffin or TED.

| Parameter | Type | Description |
|---|---|---|
| `id` (path) | string | Tender id such as `ted:123456-2026`. |

The response is `{ "data": { ...tender }, "meta": { ... } }` with the same tender fields as above.

## Search addresses $0.005

GET /v1/addresses

Search Norwegian street and cadastral addresses from Kartverket by text, municipality or postcode. Give at least one of `q`, `municipality` or `postcode`.

| Parameter | Type | Description |
|---|---|---|
| `q` | string, 2 to 100 chars | Address text, e.g. `Karl Johans gate 1`. |
| `municipality` | string, 4 digits | Municipality number, e.g. `0301` for Oslo. |
| `postcode` | string, 4 digits | Postcode, e.g. `0154`. |
| `page` | integer, 0 or more | Zero-based page. Default 0. |
| `size` | integer, 1 to 50 | Results per page. Default 10. |

```
{
  "data": [
    {
      "address": "Karl Johans gate 1", "street": "Karl Johans gate", "number": 1, "letter": null, "extra_name": null,
      "postcode": "0154", "city": "OSLO",
      "municipality": { "number": "0301", "name": "OSLO" },
      "coordinates": { "lat": 59.9112, "lon": 10.7415 },
      "cadastre": { "gnr": 208, "bnr": 12, "fnr": 0, "snr": null },
      "units": 4, "type": "road", "position_verified": false, "updated": "2024-01-01"
    }
  ],
  "page": { "page": 0, "size": 10, "total": 1 },
  "meta": { "source": "kartverket", "fetched_at": "2026-10-13T08:15:00Z", "stale": false, "license": "CC-BY-4.0", "attribution": "..." }
}
```

`letter` and `extra_name` are `null` when the address has none. `units` counts the registered units (bruksenheter) at the address. `type` is `road` or `cadastre`. `coordinates` are WGS 84 (EPSG:4258). `city` is as Kartverket gives it. `updated` is the date Kartverket last changed the record.

## Addresses near a point $0.005

GET /v1/addresses/nearby

Addresses within a radius of a point, nearest first. Each item also has `distance_m`. Coordinates must be in Norway (latitude 57 to 72, longitude 4 to 32).

| Parameter | Type | Description |
|---|---|---|
| `lat` | number, 57 to 72 | Latitude in degrees (WGS 84). |
| `lon` | number, 4 to 32 | Longitude in degrees (WGS 84). |
| `radius` | integer, 1 to 1000 | Radius in metres. Default 100. |
| `page` | integer, 0 or more | Zero-based page. Default 0. |
| `size` | integer, 1 to 50 | Results per page. Default 10. |

The response has the same shape as address search, with `distance_m` (metres from the point) on every item.

## Search laws and regulations $0.01

GET /v1/laws

Search current Norwegian laws and central regulations from Lovdata. With `q`, a document matches when every word is in its title or short title, or when every word is in one of its sections. Title matches come first, laws before regulations, then documents with more matching sections. Each result carries up to three matching sections as short snippets, never the full text. Without `q` the route lists documents by title.

| Parameter | Type | Description |
|---|---|---|
| `q` | string, 2 to 100 chars | Up to 8 words, case-insensitive, e.g. `personvern behandlingsansvarlig`. |
| `type` | `law` or `regulation` | Only laws (lov) or only central regulations (forskrift). |
| `ministry` | string, 2 to 100 chars | Part of the ministry name, e.g. `Justis`. |
| `page` | integer, 0 or more | Zero-based page. Default 0. |
| `size` | integer, 1 to 50 | Results per page. Default 10. |

```
{
  "data": [
    {
      "id": "lov/2018-06-15-38", "type": "law",
      "title": "Lov om behandling av personopplysninger (personopplysningsloven)",
      "short_title": "Personopplysningsloven – popplyl",
      "date": "2018-06-15", "ministry": "Justis- og beredskapsdepartementet",
      "last_changed": "2026-01-01", "in_force": true,
      "url": "https://lovdata.no/dokument/NL/lov/2018-06-15-38",
      "title_match": false, "section_matches": 50,
      "matches": [{ "section": "§ 4", "heading": "Behandlingsansvarlig", "snippet": "…" }]
    }
  ],
  "page": { "page": 0, "size": 10, "total": 1 },
  "meta": { "source": "lovdata", "fetched_at": "2026-10-13T05:30:00Z", "stale": false, "as_of": "2026-10-13", "license": "NLOD 2.0", "attribution": "..." }
}
```

`id` is the Lovdata id: `lov/…` for laws, `forskrift/…` for regulations. `in_force` is `false` only when a dated entry into force is still ahead. `section_matches` counts all matching sections; `matches` shows the first three. Without `q` the items have no `title_match`, `section_matches` or `matches`.

## Get one law or regulation $0.005

GET /v1/laws/{id}

Metadata and table of contents for one law or regulation: section ids, chapters and headings, without the text.

| Parameter | Type | Description |
|---|---|---|
| `id` (path) | string | Lovdata id as written in the path, slash included: `/v1/laws/lov/2005-06-17-62`. `lov-2005-06-17-62` and `lov%2F2005-06-17-62` also work. |

```
{
  "data": {
    "id": "lov/2005-06-17-62", "type": "law",
    "title": "Lov om arbeidsmiljø, arbeidstid og stillingsvern mv. (arbeidsmiljøloven)",
    "short_title": "Arbeidsmiljøloven – aml",
    "date": "2005-06-17", "ministry": "Arbeids- og inkluderingsdepartementet",
    "last_changed": "2026-07-01", "in_force": true,
    "url": "https://lovdata.no/dokument/NL/lov/2005-06-17-62",
    "section_count": 197,
    "sections": [{ "id": "§ 1-1", "chapter": "Kapittel 1. Innledende bestemmelser", "heading": "Lovens formål" }]
  },
  "meta": { "source": "lovdata", "license": "NLOD 2.0", "attribution": "..." }
}
```

An unknown id gives 404 before any payment is asked for.

## Get one section $0.005

GET /v1/laws/{id}/sections/{section}

The full text of one section (§ or artikkel). Paragraphs are separated by newlines and list items keep their labels (`a.`, `b.`).

| Parameter | Type | Description |
|---|---|---|
| `id` (path) | string | Lovdata id, as above. |
| `section` (path) | string, 1 to 60 chars | A section id from the table of contents. `§ 3-1` (URL-encoded as `%C2%A7%203-1`) and `3-1` both work. If a document repeats an id, later copies are `§ 1 (2)`, `§ 1 (3)` and so on. |

```
GET /v1/laws/lov/2005-06-17-62/sections/3-1

{
  "data": {
    "law": { "id": "lov/2005-06-17-62", "type": "law", "title": "...", "url": "https://lovdata.no/dokument/NL/lov/2005-06-17-62" },
    "section": { "id": "§ 3-1", "chapter": "Kapittel 3. Alminnelige krav til arbeidsmiljøet", "heading": "Systematisk helse-, miljø- og sikkerhetsarbeid", "text": "..." },
    "previous": "§ 2-6", "next": "§ 3-2"
  },
  "meta": { "source": "lovdata", "license": "NLOD 2.0", "attribution": "..." }
}
```

Text that Lovdata gives outside numbered sections (amending laws, annexes) is returned as sections named after their chapter heading, or `tekst`. Footnotes, amendment notes and provisions that have not entered into force are left out.

## Exchange rates $0.005

GET /v1/exchange-rates

Official Norges Bank exchange rates against the Norwegian krone. Without `date` you get the latest business day; without `currencies` you get every currency Norges Bank publishes. Use `from` and `to` together for a daily series.

| Parameter | Type | Description |
|---|---|---|
| `currencies` | string | Comma-separated ISO 4217 codes, e.g. `USD,EUR`. At most 40. Default: all. |
| `date` | string, YYYY-MM-DD | One day. Not together with `from` and `to`. |
| `from` | string, YYYY-MM-DD | Start of a period. Needs `to`. |
| `to` | string, YYYY-MM-DD | End of a period, inclusive. At most 366 days after `from`. |

```
{
  "data": {
    "date": "2026-10-02",
    "base": "NOK",
    "rates": [
      { "currency": "JPY", "unit_multiplier": 100, "nok_per_unit": 6.1198 },
      { "currency": "USD", "unit_multiplier": 1, "nok_per_unit": 9.6494 }
    ]
  },
  "meta": { "source": "norges-bank", "fetched_at": "2026-10-13T08:15:00Z", "stale": false, "requested_date": "2026-10-03", "date": "2026-10-02", "not_found": [], "license": "...", "attribution": "Source: Norges Bank." }
}
```

`nok_per_unit` is the rate as Norges Bank quotes it: NOK per `unit_multiplier` units of the currency (100 for e.g. JPY, SEK and DKK, otherwise 1). Divide by `unit_multiplier` for NOK per single unit. Norges Bank publishes on business days only. If `date` is a weekend or holiday you get the prior business day: `meta.requested_date` is what you asked for and `meta.date` (and `data.date`) is the day the rates are from. `meta.not_found` lists requested currencies without a rate. If no rate is found at all you get 404 and are not charged. The trade-weighted indices TWI and I44 are left out.

With `from` and `to` the data is `{ "base": "NOK", "series": [ { "currency": "USD", "unit_multiplier": 1, "observations": [ { "date": "2026-09-28", "nok_per_unit": 9.5069 } ] } ] }`, and `meta` has `from` and `to` instead of the dates above.

## Look up a Finnish company $0.005

GET /v1/fi/companies/{businessId}

One company from the Finnish trade register (PRH, YTJ open data), cleaned. Texts are in English where PRH provides them.

| Parameter | Type | Description |
|---|---|---|
| `businessId` (path) | string, `NNNNNNN-N` | Finnish business ID (Y-tunnus), mod-11 checked, e.g. `0112038-9`. Without the dash (`01120389`) also works. |

```
{
  "data": {
    "business_id": "0112038-9", "eu_id": "FIFPRO.0112038-9", "name": "Nokia Oyj",
    "other_names": [ { "name": "Nokia Networks", "type": "auxiliary" }, { "name": "Nokia Abp", "type": "parallel" } ],
    "company_form": { "code": "OYJ", "description": "Public limited company" },
    "status": "active",
    "industry": { "code": "70100", "description": "Activities of head offices" },
    "address": { "street": "Karakaari 7", "postcode": "02610", "city": "ESPOO", "country": "FI" },
    "website": "www.nokia.com", "registered": "1896-12-19", "ended": null,
    "trade_register_status": { "code": "1", "description": "Registered" },
    "last_modified": "2026-08-19T10:04:06"
  },
  "meta": { "source": "prh", "fetched_at": "2026-10-13T08:15:00Z", "stale": false, "license": "CC-BY-4.0", "attribution": "..." }
}
```

`status` is `active`, `pending` (business ID not yet in force), `restructuring`, `winding_up` (liquidation), `bankrupt` or `ceased`. `other_names` lists current parallel (`parallel`) and auxiliary (`auxiliary`, `auxiliary_translation`) names; former names are left out. `industry` is the main line of business in TOL 2008, the Finnish version of NACE. `company_form.code` is PRH's abbreviation (OY, OYJ, KY, OK and so on) and can be used as the `company_form` filter. `address` is the street address, or the postal address when there is none. `registered` is the first registration date, `ended` the date of dissolution. An unknown business ID gives 404 and is not charged.

## Search Finnish companies $0.01

GET /v1/fi/companies

Search Finnish companies by name (current and former names), town or city, company form, industry and postcode. Give at least one filter. Each page holds up to 100 hits.

| Parameter | Type | Description |
|---|---|---|
| `q` | string, 2 to 100 chars | Name, or part of it. |
| `municipality` | string, 2 to 100 chars | Town or city name, e.g. `Helsinki`. |
| `company_form` | string | PRH company form code, e.g. `OY`, `OYJ`, `KY`, `OK`, `SÄÄ`. |
| `industry` | string, 2 to 5 digits | TOL 2008 code or prefix, e.g. `62` or `62010`. |
| `postcode` | string, 5 digits | Postcode, e.g. `00100`. |
| `page` | integer, 0 or more | Zero-based page. Default 0. |

The response has the same envelope as the lookup, with a list of the records above in `data` and `page` as `{ "page": 0, "size": 100, "total": 991 }`. `size` is PRH's page size; a page can hold fewer hits. `total` is the number of hits for the whole search.

## Free routes $0

- `GET /v1/health`: service and data freshness status. Returns 503 when degraded.
- `GET /openapi.json`: OpenAPI 3.1 description of the API.
- `POST /mcp`: MCP server. Listing tools is free.

## Tender alerts $0.25 / 30 days

Register a tender search once and get new matching tenders pushed to your webhook after each nightly refresh of Doffin and TED. One payment covers 30 days.

```
POST https://veldora.no/v1/alerts?webhook=https%3A%2F%2Fexample.com%2Fhook&cpv=72&region=NO08
```

Settings go in the query string: `webhook` (https, public host) and at least one of `q`, `cpv`, `region` and `buyer_orgnr`. You pay with x402 as for any other route. The `201` response holds the alert `id` and a `secret`. We show the secret only once, so store it.

Each delivery is a `POST` with JSON `{"alert_id", "sent_at", "tenders": [...]}`, up to 50 open tenders published since you created the alert, each sent once. The `Veldora-Signature` header reads `t=<unix time>,v1=<hex>`, where `v1` is HMAC-SHA256 of `t + "." + body` with your secret. Check it before you trust a delivery.

- Answer with any 2xx status. After 5 failed deliveries in a row we switch the alert off.
- `GET /v1/alerts/{id}` shows status and the last delivery. `DELETE /v1/alerts/{id}` removes the alert. Both are free and need `Authorization: Bearer <secret>`.
- We refuse webhooks on private networks and don't follow redirects.

## Errors

Errors from the data routes share one shape. Branch on `error.code`, not on the message.

```
{ "error": { "code": "invalid_orgnr", "message": "...", "retryable": false, "docs": "https://veldora.no/docs#errors" } }
```

Every error body has exactly these four fields: `code`, `message`, `retryable` and `docs`. The content type is `application/json`. Unknown paths under `/v1` return a JSON 404 with code `not_found`.

Payment errors come back as `402` with a fresh `PAYMENT-REQUIRED` header. The reason is in the body field `error`, with a readable text in `errorMessage`. Sign a new payment for these, unless noted.

| Status | Code | Meaning |
|---|---|---|
| 400 | `invalid_orgnr`, `invalid_id`, `invalid_parameter` | A parameter is malformed or out of range. Not charged. |
| 400 | `missing_filter` | Company search needs at least one of `q`, `municipality`, `nace`, `org_form`. |
| 400 | `enk_name_search` | Name search is not offered for sole proprietorships. |
| 400 | `paging_limit` | The page is past the 10 000 hit limit. Narrow the search. |
| 400 | `unknown_parameter` | The query has a parameter the route does not accept. Not charged. |
| 404 | `not_found` | No such organisation or tender, or an unknown path under `/v1`. Not charged. |
| 402 | `invalid_payment_header` | The header is not base64-encoded JSON. |
| 402 | `unsupported_x402_version` | Use x402 version 2 and the `PAYMENT-SIGNATURE` header. |
| 402 | `unsupported_scheme`, `unsupported_payload`, `invalid_payment_payload`, `invalid_signature_format` | The payload is not an `exact` EIP-3009 USDC authorization in the expected shape. |
| 402 | `wrong_network` | Pay on Base mainnet, `eip155:8453`. |
| 402 | `wrong_asset` | Pay in the USDC contract named in the requirements. |
| 402 | `wrong_recipient` | The authorization pays a different address. |
| 402 | `underpayment` | The amount is below the price. |
| 402 | `amount_mismatch` | The amount must equal the price exactly. |
| 402 | `payment_expired` | The authorization expires too soon or has expired. Sign a new one. |
| 402 | `payment_not_yet_valid` | `validAfter` is in the future. |
| 402 | facilitator reason, e.g. `insufficient_funds` | The facilitator rejected the payment. The reason is passed through unchanged. |
| 402 | `payment_already_used` | This authorization was used for a different request. |
| 402 | `payment_failed` | This payment could not be settled earlier. Sign a new one. |
| 402 | `settlement_failed` | Settlement failed. Data was withheld. Sign a new payment. |
| 409 | `payment_in_progress` | The same payment is being processed. Retry the same header shortly. |
| 429 | `rate_limited` | Too many requests. Wait for `Retry-After` seconds. |
| 503 | `data_unavailable` | Tender data is not loaded yet. Not charged. |
| 503 | `upstream_unavailable` | The company register does not answer. Not charged. |
| 503 | `facilitator_unavailable` | The payment facilitator did not answer. Not charged. Retry later. |
| 503 | `settlement_pending` | Settlement has not confirmed yet. Retry the same header. |
| 503 | `payments_unavailable` | Payments are not available right now. Retry later. |
| 503 | `temporarily_unavailable` | Something we depend on failed before settlement. Not charged. |
| 500 | `internal_error` | A bug on our side. Not charged. |

## Retries and idempotency

- Resending the same `PAYMENT-SIGNATURE` for the same request returns the same response. You are not charged twice.
- On `409 payment_in_progress` or `503 settlement_pending`, wait for `Retry-After` and retry with the **same** header. Do not sign a new payment.
- On other 503 codes you were not charged. Retry later with the same or a new payment.
- Do not reuse one authorization for a different request. That returns `payment_already_used`.
- Authorizations carry a nonce and an expiry. Keep the expiry long enough to cover retries.

## Rate limits

Each client IP can send 120 requests per minute. Every API response carries the limit state, so a client can slow down before it is blocked.

| Header | Meaning |
|---|---|
| `RateLimit-Policy` | The policy, for example `120;w=60` (120 requests per 60 seconds). IETF draft-ietf-httpapi-ratelimit-headers. |
| `RateLimit` | The current state in the same draft format: remaining requests and seconds until reset. |
| `RateLimit-Limit` | Requests allowed in the window. |
| `RateLimit-Remaining` | Requests left in the window. |
| `RateLimit-Reset` | Seconds until the window resets. |
| `Retry-After` | On 429 only: seconds to wait before the next request. |

A request over the limit gets `429 rate_limited` and costs nothing.

## Data notes

- **Companies** are fetched live from Brønnøysundregistrene. During outages we answer from an offline mirror and set `meta.stale` to `true`.
- **Tenders** are refreshed daily from Doffin and TED.
- **Addresses** are fetched live from Kartverket (the national address register). There is no offline mirror: if Kartverket does not answer you get 503 and are not charged.
- **Laws and regulations** are the current ("gjeldende") laws and central regulations from Lovdata's open data, rebuilt every night. Historical and repealed texts, local regulations and court decisions are not included. These are Lovdata's consolidated texts, not the official publication (Norsk Lovtidend), and not legal advice. `meta.as_of` is the date of Lovdata's data. If the data has not been loaded you get 503 and are not charged.
- **Finnish companies** are fetched live from the Finnish Patent and Registration Office (PRH, the YTJ open data). There is no offline mirror: if PRH does not answer you get 503 and are not charged. Descriptions come from PRH's code lists in English, with Finnish as the fallback. Veldora serves Finnish companies only by business ID or search; it is the first dataset outside Norway.
- Every data response has the headers `X-Data-Source`, `X-Data-Fetched-At` and `X-Data-Stale` (`true` or `false`).
- **No roles.** Veldora returns no board members, managers or other roles in v1, and no phone or e-mail.
- **Sole proprietorships (ENK)** are named after a person. They are returned with `name: null`, `name_masked: true`, no street address and no website. They are left out of name search.
- **Paging limit.** Brønnøysundregistrene stops at 10 000 hits. `(page + 1) × size` may not exceed 10 000. Narrow the search with more filters. Requests past the limit get 400 before payment.

## MCP

Endpoint: `https://veldora.no/mcp`, streamable HTTP. Listing tools is free. Calling one costs the same as the matching REST route.

| Tool | Route | Price |
|---|---|---|
| `get_company` | GET /v1/companies/{orgnr} | $0.005 |
| `search_companies` | GET /v1/companies | $0.01 |
| `get_company_tenders` | GET /v1/companies/{orgnr}/tenders | $0.01 |
| `get_company_awards` | GET /v1/companies/{orgnr}/awards | $0.01 |
| `search_tenders` | GET /v1/tenders | $0.01 |
| `get_tender` | GET /v1/tenders/{id} | $0.005 |
| `search_addresses` | GET /v1/addresses | $0.005 |
| `addresses_near` | GET /v1/addresses/nearby | $0.005 |
| `search_laws` | GET /v1/laws | $0.01 |
| `get_law` | GET /v1/laws/{id} | $0.005 |
| `get_law_section` | GET /v1/laws/{id}/sections/{section} | $0.005 |
| `get_exchange_rates` | GET /v1/exchange-rates | $0.005 |
| `get_finnish_company` | GET /v1/fi/companies/{businessId} | $0.005 |
| `search_finnish_companies` | GET /v1/fi/companies | $0.01 |

Payment follows the x402 MCP transport:

1. Call a tool without payment. The result has `isError: true` and the `PaymentRequired` object in `structuredContent`.
2. Sign the payment as for REST.
3. Call the tool again with the payload in `_meta["x402/payment"]`.
4. The result carries the settlement receipt in `_meta["x402/payment-response"]`.

The `@x402/mcp` client does this for you. If the arguments are wrong, the tool returns `isError` with `invalid_argument` before asking for payment.

```
{
  "mcpServers": {
    "veldora": { "url": "https://veldora.no/mcp" }
  }
}
```

## Command line

The `veldora` command calls the API from a terminal. It reads the price from the 402 first and refuses to pay more than `--max-price` (default $0.01). Set `VELDORA_PRIVATE_KEY` to a wallet key holding USDC on Base to pay.

```
npm install -g https://veldora.no/cli/veldora-cli-1.1.0.tgz
veldora company 923609016 --dry-run    # show the price, pay nothing
VELDORA_PRIVATE_KEY=0x… veldora company 923609016
VELDORA_PRIVATE_KEY=0x… veldora tenders --cpv 72 --open --size 5
```

Commands: `company`, `companies`, `company-tenders`, `company-awards`, `tenders`, `tender`, `addresses`, `addresses-nearby`, `laws`, `law`, `law-section`, `rates`, `fi-company`, `fi-companies` and `health`. Run `veldora --help` for all options. Without installing: `npx -y --package=https://veldora.no/cli/veldora-cli-1.1.0.tgz veldora health`.

## Versioning and deprecation

- The API is versioned in the path. Today that is `/v1`.
- Breaking changes ship only in a new version, such as `/v2`. `/v1` keeps its behaviour.
- A deprecated version or endpoint is announced at least 90 days before it is removed. The notice appears in these docs and in [llms.txt](https://veldora.no/llms.txt). The affected responses also carry a `Deprecation` header (RFC 9745), a `Sunset` header with the removal date (RFC 8594) and a `Link: <…>; rel="deprecation"` header that points to the notice.
- Additive changes can happen at any time: new fields, new endpoints, new error codes. Clients must ignore unknown fields and treat an unknown error code by its HTTP status and the `retryable` flag.

## Markdown for agents

Any page on veldora.no can be fetched as Markdown. Send `Accept: text/markdown`:

```
curl -H "Accept: text/markdown" https://veldora.no/docs
```

The Markdown versions are also available at [/index.md](https://veldora.no/index.md), [/docs.md](https://veldora.no/docs.md), [/about.md](https://veldora.no/about.md), [/contact.md](https://veldora.no/contact.md) and [/personvern.md](https://veldora.no/personvern.md). They have the same content as the HTML pages.

## Attribution and licences

- Company data: Contains data under the Norwegian licence for Open Government data (NLOD) distributed by Brønnøysundregistrene. See [NLOD 2.0](https://data.norge.no/nlod/en/2.0).
- Tender data: Doffin, the Norwegian national notification database, © DFØ, licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
- Tender data: Tenders Electronic Daily (TED), © European Union, [ted.europa.eu](https://ted.europa.eu). Reuse is authorised under Commission Decision 2011/833/EU.
- Address data: Contains address data from Kartverket, © Kartverket, licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
- Laws and regulations: Contains data from Lovdata (Stiftelsen Lovdata, [lovdata.no](https://lovdata.no)): current laws and central regulations, licensed under [NLOD 2.0](https://data.norge.no/nlod/en/2.0). Lovdata asks users to credit it.
- Exchange rates: Source: Norges Bank. Norges Bank allows copying with the bank named as the source; see its [copyright notice](https://www.norges-bank.no/en/disclaimer/).
- Finnish company data: Contains data from the Finnish Patent and Registration Office (PRH), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
- Every response repeats the right notice in `meta.attribution`. Pass it on when you republish the data.

## Network

Veldora takes payment in USDC on Base mainnet (`eip155:8453`), with real money. Prices are in USD, and one USDC is one US dollar. You do not need ETH, because the facilitator pays the gas. The amount in the 402 response is in atomic units with 6 decimals, so $0.005 is `5000`.

## Links

- [Docs](https://veldora.no/docs) ([Markdown](https://veldora.no/docs.md))
- [llms.txt](https://veldora.no/llms.txt)
- [OpenAPI](https://veldora.no/openapi.json)
- [Sitemap](https://veldora.no/sitemap.xml)
