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 · Norwegian public tenders · Norwegian public contract awards · Tender alerts · Norwegian addresses · Norwegian laws and regulations · NOK exchange rates
- Quickstart
- Payment
- Endpoints
- Tender alerts
- Errors
- Retries
- Rate limits
- Data notes
- MCP
- Command line
- Versioning and deprecation
- Markdown for agents
- Licences
- Network
Quickstart
- 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.
- Install the client:
npm install @x402/fetch @x402/evm viem - Wrap
fetchand 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.
- Send the request without payment. Veldora replies
402. ThePAYMENT-REQUIREDheader holds base64-encoded JSON with the requirements. The body has the same JSON. - Pick the entry in
accepts: schemeexact, networkeip155:8453, the USDC contract, the exact amount in atomic units (6 decimals) and the recipient. - Sign an EIP-3009
transferWithAuthorizationfor that amount. The facilitator submits it on chain and pays the gas. - Repeat the same request with the base64 payload in the
PAYMENT-SIGNATUREheader. - On 200 you get the data and a
PAYMENT-RESPONSEheader 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_mismatchtoo.
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.
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®ion=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 needAuthorization: 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-SIGNATUREfor the same request returns the same response. You are not charged twice. - On
409 payment_in_progressor503 settlement_pending, wait forRetry-Afterand 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.staletotrue. - 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_ofis 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-AtandX-Data-Stale(trueorfalse). - 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) × sizemay 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:
- Call a tool without payment. The result has
isError: trueand thePaymentRequiredobject instructuredContent. - Sign the payment as for REST.
- Call the tool again with the payload in
_meta["x402/payment"]. - 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./v1keeps 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. The affected responses also carry a
Deprecationheader (RFC 9745), aSunsetheader with the removal date (RFC 8594) and aLink: <…>; 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
retryableflag.
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, /docs.md, /about.md, /contact.md and /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.
- Tender data: Doffin, the Norwegian national notification database, © DFØ, licensed under CC BY 4.0.
- Tender data: Tenders Electronic Daily (TED), © European Union, 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.
- Laws and regulations: Contains data from Lovdata (Stiftelsen Lovdata, lovdata.no): current laws and central regulations, licensed under NLOD 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.
- Finnish company data: Contains data from the Finnish Patent and Registration Office (PRH), licensed under CC 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.