FirmaAPIFirmaAPI

API Dokumentation

Alt du behøver for at integrere danske virksomhedsdata i din applikation.

OpenAPI-spec: /api/openapi

Kom i gang

Du kan integrere FirmaAPI i din applikation på under 5 minutter. Tre simple trin:

1

Opret konto

Opret en gratis konto og modtag din API-nøgle med det samme.

2

Tilføj API-nøgle

Send din API-nøgle som Bearer token i Authorization headeren.

3

Kald API'et

Foretag dit første kald og modtag virksomhedsdata som JSON.

Dit første API-kaldbash
curl https://firmaapi.dk/api/v1/company/24256790 \
  -H "Authorization: Bearer cvr_live_din_api_nøgle"

Autentifikation

Alle API-kald kræver en gyldig API-nøgle. Du kan sende nøglen på to måder:

Metode 1: Authorization header (anbefalet)
Authorization: Bearer cvr_live_abc123def456...
Metode 2: X-API-Key header
X-API-Key: cvr_live_abc123def456...

Om API-nøgler

  • Nøgler starter med cvr_live_ prefix og er 49 tegn lange
  • Du kan oprette flere nøgler fra dit dashboard
  • Nøgler kan deaktiveres og genaktiveres uden at slette dem
  • Hold dine nøgler hemmelige – del dem aldrig i offentlig kode

Sikkerhed: Kald aldrig API'et direkte fra klient-side kode (browser/app). Brug altid en server-side proxy, så din API-nøgle forbliver hemmelig.

Base URL

Alle API-kald foretages mod følgende base URL:

https://firmaapi.dk/api/v1/

Miljøer

Productionhttps://firmaapi.dk/api/v1/

Alle data og infrastruktur er hosted i EU. GDPR-compliant.

Sandbox / test-nøgler

Test din integration uden at bruge betalte API-kald. Tag din live-nøgle og erstat cvr_live_ med cvr_test_:

Live → Test nøgle
# Din live-nøgle:
cvr_live_abc123def456...

# Din test-nøgle (samme nøgle, andet prefix):
cvr_test_abc123def456...
Eksempel med test-nøglebash
curl https://firmaapi.dk/api/v1/company/24256790 \
  -H "Authorization: Bearer cvr_test_abc123def456..."

Hvad gør test-nøgler?

  • Returnerer syntetisk data med alle enterprise-felter (ejere, regnskab, historik, GPS m.m.)
  • Ingen rate limits, ingen kvote-forbrugt — perfekt til integrationstest
  • Response inkluderer X-Sandbox: true og X-Billing-Counted: false headers
  • Alle endpoints understøttes: company, search, batch, person, regnskab, webhooks

CVR-opslag

Hent komplet virksomhedsdata via et 8-cifret CVR-nummer. For de fleste integrationer er dette det primære endpoint, og include= bruges kun til ekstra, tunge datablokke.

GET/api/v1/company/{cvr}
ParameterTypeBeskrivelse
cvr*string8-cifret CVR-nummer (i URL path)
Eksempelbash
curl https://firmaapi.dk/api/v1/company/24256790 \
  -H "Authorization: Bearer cvr_live_..."
Eksempel med include=bash
curl "https://firmaapi.dk/api/v1/company/24256790?include=history,finance,production_units,relations" \
  -H "Authorization: Bearer cvr_live_..."
ResponseJSON
{
  "cvr": "24256790",
  "name": "Novo Nordisk A/S",
  "address": "Novo Allé 1",
  "zipcode": "2880",
  "city": "Bagsværd",
  "status": "Aktiv",
  "company_type": "Aktieselskab",
  "industry_code": "212000",
  "industry_text": "Fremstilling af farmaceutiske præparater",
  "founded": "1989-01-01",
  "employees": "1000+",
  "phone": "44448888",
  "email": null,
  "website": "https://www.novonordisk.com",
  "ad_protected": false,
  "secondary_industries": [
    { "code": "721100", "text": "Forskning og udvikling inden for bioteknologi" }
  ],
  "capital": 500000000,
  "capital_currency": "DKK",
  "signing_rule": "Selskabet tegnes af bestyrelsens formand...",
  "production_unit_count": 12,
  "relations": {
    "parents": [
      { "cvr": "24257630", "name": "Novo Holdings A/S", "ownership_share": 75, "voting_share": 75, "relation_type": "parent", "inferred": true }
    ],
    "subsidiaries": [
      { "cvr": "12345678", "name": "Eksempel Datter A/S", "ownership_share": 100, "voting_share": 100, "relation_type": "subsidiary", "inferred": true }
    ]
  },
  "owners": [
    { "name": "Novo Holdings A/S", "title": "Reel ejer" },
    { "name": "Lars Fruergaard Jørgensen", "title": "Direktør, Bestyrelsesmedlem" }
  ],
  "meta": {
    "cached_at": "2025-01-15T12:00:00Z",
    "source": "FirmaAPI"
  }
}

Bemærk: owners feltet er kun inkluderet for Basis plan eller højere. Gratis-brugere vil ikke se dette felt i response.

Tip: Response inkluderer en X-Data-Age header med alderen på cached data i sekunder.

Anbefaling: Start med /api/v1/company/{cvr}. Company-responsen indeholder allerede alle plan-tilgængelige virksomhedsfelter. Brug kun include= til ekstra blokke som historik, regnskaber og produktionsenheder.

Anbefalede include-blokke

Disse blokke hentes kun når du eksplicit beder om dem med include=. Det holder standardresponsen hurtig og slank.

IncludeIndholdPlan
history
Historik
Henter historiske ændringer for navn, adresse, status, branche, ansatte og binavne (Standard+).
Basis+
finance
Regnskaber
Henter regnskabsdata som ekstra blok, inkl. nøgletal (Standard+), årsændringer og udvidede felter (Professionel+).
Standard+
production_units
Produktionsenheder
Henter P-enheder som separat blok, så company-responsen kan holdes slank.
Standard+
relations
Koncernrelationer
Henter direkte moder-/datterselskabsrelationer som en ekstra blok.
Professionel+
events
Hændelser
Henter virksomhedens livscyklus, fusioner/spaltninger og en afledt hændelsestidslinje.
Standard+

Felter der følger automatisk med

Disse datablokke returneres automatisk på company-endpointet, når planen giver adgang. Du behøver ikke include= for dem.

DataFelterPlan
Ejere og ledelse
Følger automatisk med på company-endpointet for Basis og højere.
owners
Basis+
Revisor-information
Følger automatisk med på company-endpointet for Professionel og Enterprise.
auditors
Professionel+
Ejerskabshistorik
Følger automatisk med på company-endpointet for Professionel og Enterprise.
ownership_history
Professionel+
GPS-koordinater
Latitude og longitude følger automatisk med på company-endpointet.
latitudelongitude
Professionel+
Udvidede kontaktoplysninger
Alle e-mailadresser, alle telefonnumre og fax følger automatisk med.
all_emailsall_phonesfax
Professionel+
Udvidede virksomhedsattributter
Audit form, regnskabsårsstart, kapital og tegningsregel følger automatisk med.
audit_formfiscal_year_startcapitalcapital_currencysigning_rule
Professionel+
Historik over sekundære brancher
Sekundær branchehistorik følger automatisk med på company-endpointet.
secondary_industry_history
Professionel+
ERST-attributter (alle planer)
Formål, binavne, regnskabsårsslut, regnskabsklasse og årlig beskæftigelse følger automatisk med.
purposesecondary_namesfiscal_year_endaccounting_classannual_employees
Basis+
ERST-attributter (Standard+)
Arbejdsgiver-/momsregistrering, import/eksport-status, punktafgifter og første regnskabsperiode.
employer_registration_datevat_registration_dateimport_registeredexport_registeredexcise_registrationsfirst_fiscal_period_end
Standard+

Batch-opslag

Slå op til 100–500 virksomheder op i en enkelt request (afhængigt af plan). Kræver Basis eller højere. Perfekt til databerigelse, CRM-integration og massevalidering.

POST/api/v1/company/batch

Request body (JSON)

ParameterTypeBeskrivelse
cvr_numbers*string[]Array af CVR-numre (max afhænger af plan: Basis 100, Standard 200, Professionel 500)
Eksempelbash
curl -X POST https://firmaapi.dk/api/v1/company/batch \
  -H "Authorization: Bearer cvr_live_..." \
  -H "Content-Type: application/json" \
  -d '{"cvr_numbers": ["24256790", "54562519", "25313763"]}'
ResponseJSON
{
  "results": [
    { "cvr": "24256790", "name": "Novo Nordisk A/S", ..., "meta": { "cached_at": "...", "source": "FirmaAPI" } },
    { "cvr": "54562519", "name": "LEGO A/S", ..., "meta": { "cached_at": "...", "source": "FirmaAPI" } },
    { "cvr": "25313763", "name": "Nets Denmark A/S", ..., "meta": { "cached_at": "...", "source": "FirmaAPI" } }
  ],
  "errors": [],
  "total": 3,
  "quota_used": 3
}

Bemærk: owners feltet er kun inkluderet for Basis plan eller højere. Gratis-brugere vil ikke se dette felt i response.

Tip: Hvert CVR-nummer i et batch-kald tæller som ét opslag mod din månedlige kvote. Feltet quota_used i response viser det samlede antal opslag brugt.

Ejere & ledelse

Ejer- og ledelsesdata følger direkte med på /api/v1/company/{cvr} for Basis og højere. Du behøver ikke et separat endpoint for at hente disse data.

Plan-adgang: Gratis = ingen adgang. Basis, Standard, Professionel og Enterprise = ejere og ledelse på company-endpointet.

Eksempelbash
curl https://firmaapi.dk/api/v1/company/24256790   -H "Authorization: Bearer cvr_live_..."
ResponseJSON
{
  "cvr": "24256790",
  "name": "Novo Nordisk A/S",
  "owners": [
    {
      "name": "Novo Holdings A/S",
      "title": "Reel ejer",
      "ownership_share": 28.1,
      "voting_share": 76.5,
      "joined_date": "1999-01-01",
      "left_date": null,
      "participant_cvr": "24257630",
      "participant_enhedsnummer": null,
      "person_id": null
    },
    {
      "name": "Lars Fruergaard Jørgensen",
      "title": "Direktør, Bestyrelsesmedlem",
      "ownership_share": null,
      "voting_share": null,
      "joined_date": "2017-01-01",
      "left_date": null,
      "participant_cvr": null,
      "participant_enhedsnummer": "4000724313",
      "person_id": "123e4567-e89b-12d3-a456-426614174000"
    }
  ],
  "ownership_history": [
    {
      "name": "Historisk Holding A/S",
      "title": "Reel ejer",
      "joined_date": "2019-01-01",
      "left_date": "2021-12-31",
      "active": false,
      "person_id": null
    }
  ]
}

Professionel+: ownership_share, voting_share, joined_date, left_date, participant_cvr, participant_enhedsnummer, person_id og ownership_history returneres kun for Professionel og Enterprise.

Personprofil-link: person_id er en UUID der kan sendes direkte til GET /api/v1/person/{id}. Feltet er null hvis deltageren er en virksomhed (fx et holdingselskab) eller endnu ikke er matchet til en personprofil. Samme felt findes på auditors og ownership_history.

Personprofiler & søgning

Personprofiler

Ejer- og ledelsesdata kan kobles til personprofiler, så du kan følge samme person på tværs af flere virksomheder. Personrelationer er tilgængelige via API og vises også offentligt på /person/{id}/{slug}.

Personprofiler bruges til krydsopslag: find personen én gang, og få hele listen af virksomheder personen deltager i.

Sådan får du fat i et person_id: Kald GET /api/v1/company/{cvr} som Professionel/Enterprise-bruger. Hver post i owners, auditors og ownership_history får et person_id-felt (UUID), som du kan sende direkte videre til /api/v1/person/{id}. Feltet er null, hvis deltageren er et selskab eller endnu ikke er matchet til en profil.

GET/api/v1/person/{id}
ParameterTypeBeskrivelse
id*string (UUID)Personprofil-UUID fra person_id-feltet på owners/auditors/ownership_history i company-responsen. Dette er ikke CVR-enhedsnummeret.
Eksempelbash
curl https://firmaapi.dk/api/v1/person/123e4567-e89b-12d3-a456-426614174000   -H "Authorization: Bearer cvr_live_..."
ResponseJSON
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "canonical_name": "Tommy Ahlers",
  "slug": "tommy-ahlers",
  "active_company_count": 12,
  "historical_company_count": 8,
  "total_company_count": 20,
  "company_count": 20,
  "affiliations": [
    {
      "cvr": "24256790",
      "company_name": "Eksempel A/S",
      "company_slug": "eksempel-as-24256790",
      "role_title": "Bestyrelsesmedlem",
      "joined_date": "2023-01-01",
      "left_date": null,
      "duration_days": 1234
    }
  ]
}

Aktive vs historiske roller: active_company_count tæller virksomheder hvor personen p.t. er registreret med en åben rolle (uden left_date) — brug det hvis du kun vil vise nuværende ledelses- og bestyrelsesposter. historical_company_count dækker tidligere, lukkede roller. For nogle advokater kan dette tal være meget højt pga. skuffeselskabs-mønsteret: en advokat står som initial direktør i et nyoprettet ApS i få dage, indtil selskabet sælges til en klient. Brug duration_days >= 180affiliations hvis du kun vil se reelle ledelsesrelationer.

Sådan bruger du persontilknytning i API'et

  • Hent virksomhed via /api/v1/company/{cvr} og brug personlink til videre opslag.
  • Hent personens samlede virksomhedsnetværk via /api/v1/person/{id}.
  • Brug affiliations[].cvr til overvågning, scoring, relationstræ og netværksanalyse.

Plan-adgang: Gratis/Basis = ingen adgang. Professionel/Enterprise = adgang.

Regnskabsdata

Regnskabsdata hentes via include=finance på company-endpointet. Det holder standardresponsen hurtig og gør integrationen enklere.

Plan-adgang: Gratis/Basis = ingen adgang. Standard = seneste årsregnskab + PDF-link + nøgletal (ratios) + udvidet P&L/balance (personaleomkostninger, varebeholdninger m.m.). Professionel/Enterprise = alle regnskabsfelter, pengestrømsopgørelse, revisionspåtegning, kvartals-/delårsrapporter, nøgletal + årsændringer (growth).

Eksempelbash
curl "https://firmaapi.dk/api/v1/company/24256790?include=finance"   -H "Authorization: Bearer cvr_live_..."
ResponseJSON
{
  "cvr": "24256790",
  "has_financials": true,
  "financials_pending": false,
  "years_included": "5",
  "financials": [
    {
      "year": 2024,
      "period_start": "2024-01-01",
      "period_end": "2024-12-31",
      "report_type": "annual",
      "revenue": 290324000000,
      "gross_profit": 245891000000,
      "profit_before_tax": 112456000000,
      "net_profit": 83214000000,
      "equity": 156789000000,
      "total_assets": 312456000000,
      "ebitda": 138200000000,
      "cash": 42300000000,
      "share_capital": 500000000,
      "currency": "DKK",
      "report_url": "http://regnskaber.virk.dk/...",
      "ratios": {
        "profit_margin": 0.2866,
        "gross_margin": 0.8469,
        "return_on_assets": 0.2663,
        "return_on_equity": 0.5308,
        "solvency_ratio": 0.5018,
        "debt_to_equity": 0.9928
      },
      "growth": {
        "revenue_yoy": 0.2351,
        "profit_yoy": 0.1842,
        "equity_yoy": 0.1523,
        "total_assets_yoy": 0.0891
      }
    },
    {
      "year": 2024,
      "period_start": "2024-07-01",
      "period_end": "2024-09-30",
      "report_type": "quarterly",
      "revenue": 78500000000,
      "currency": "DKK"
    }
  ]
}

Nøgletal og årsændringer: Alle værdier i ratios og growth er decimaler — gang med 100 for procent (f.eks. 0.2866 = 28,66%). Felter er null hvis de underliggende regnskabstal mangler eller divisor er 0.

Fejl (Gratis/Basis plan)JSON
{
  "error": {
    "code": "PLAN_UPGRADE_REQUIRED",
    "include": "finance",
    "message": "Regnskabsdata kræver Standard, Professionel eller Enterprise plan.",
    "current_plan": "gratis",
    "required_plan": "standard"
  }
}

Historik

Historiske ændringer hentes via include=history på company-endpointet. Her får du navn, adresse, status, branche, ansatte og virksomhedsform samlet i én blok.

Plan-adgang: Gratis = ingen adgang. Basis og højere = historik via include=history.

Eksempelbash
curl "https://firmaapi.dk/api/v1/company/24256790?include=history"   -H "Authorization: Bearer cvr_live_..."
ResponseJSON
{
  "cvr": "24256790",
  "name": "Novo Nordisk A/S",
  "history": {
    "names": [
      { "name": "Novo Nordisk A/S", "valid_from": "1999-01-01", "valid_to": null }
    ],
    "addresses": [
      { "address": "Novo Allé 1", "zipcode": "2880", "city": "Bagsværd", "municipality_code": 159, "municipality_name": "Gladsaxe", "valid_from": "2002-10-05", "valid_to": null }
    ],
    "statuses": [
      { "status": "Aktiv", "valid_from": "1989-01-01", "valid_to": null }
    ],
    "industries": [
      { "code": "212000", "text": "Fremstilling af farmaceutiske præparater", "valid_from": "2008-01-01", "valid_to": null }
    ],
    "employees": [
      { "employees": "1000+", "valid_from": "2024-01-01", "valid_to": null }
    ],
    "company_forms": [
      { "form": "Aktieselskab", "valid_from": "1989-01-01", "valid_to": null }
    ]
  }
}
Fejl (Gratis plan)JSON
{
  "error": {
    "code": "PLAN_UPGRADE_REQUIRED",
    "include": "history",
    "message": "Historikdata kræver Basis plan eller højere.",
    "current_plan": "gratis",
    "required_plan": "basis"
  }
}

Hændelser

Hent virksomhedens livscyklus, fusioner, spaltninger og en afledt hændelsestidslinje via include=events på company-endpointet.

Plan-adgang: Gratis/Basis = ingen adgang. Standard+ og højere = fuld adgang. Webhook-events (company.merged, company.demerged) kræver Professionel+.

Eksempel

Requestbash
curl "https://firmaapi.dk/api/v1/company/24256790?include=events" \
  -H "X-API-Key: cvr_live_..."
Response (udsnit)JSON
{
  "lifecycle": [
    { "valid_from": "1923-02-01", "valid_to": null }
  ],
  "mergers": [
    { "type": "merger", "effective_date": "1990-01-01" },
    { "type": "demerger", "effective_date": "2000-11-07" }
  ],
  "events": [
    { "type": "founded", "occurred_at": "1923-02-01", "description": "Virksomheden stiftet" },
    { "type": "merged", "occurred_at": "1990-01-01", "description": "Fusion registreret" },
    { "type": "demerged", "occurred_at": "2000-11-07", "description": "Spaltning registreret" }
  ]
}
ParameterTypeBeskrivelse
lifecycle[]arrayLivsperioder (valid_from, valid_to). Flere perioder = virksomheden har været ophørt og genoptaget.
mergers[]arrayFusioner (type=merger) og spaltninger (type=demerger) med dato for registrering.
events[]arrayAfledt tidslinje: founded, dissolved, reopened, merged, demerged med dato og beskrivelse.

Produktionsenheder

Hent virksomhedens P-enheder via include=production_units på company-endpointet. Brug kun det separate P-nummer-endpoint, når du allerede kender den konkrete enhed.

Plan-adgang: Gratis/Basis = ingen adgang. Standard = P-enheder. Professionel/Enterprise = udvidede P-enhedsfelter.

Eksempelbash
curl "https://firmaapi.dk/api/v1/company/24256790?include=production_units"   -H "Authorization: Bearer cvr_live_..."
ResponseJSON
{
  "cvr": "24256790",
  "name": "Novo Nordisk A/S",
  "production_unit_count": 3,
  "production_units": [
    {
      "p_number": "1234567890",
      "name": "Novo Nordisk - Bagsværd",
      "address": "Novo Allé 1",
      "zipcode": "2880",
      "city": "Bagsværd",
      "industry_code": "212000",
      "industry_text": "Fremstilling af farmaceutiske præparater",
      "employees": "1000+"
    }
  ]
}
Fejl (Gratis/Basis plan)JSON
{
  "error": {
    "code": "PLAN_UPGRADE_REQUIRED",
    "include": "production_units",
    "message": "Produktionsenheder kræver Standard plan eller højere.",
    "current_plan": "gratis",
    "required_plan": "standard"
  }
}

Enkelt P-enhed opslag

GET/api/v1/production-unit/{p_number}
ParameterTypeBeskrivelse
p_number*string10-cifret P-nummer (i URL path)
ResponseJSON
{
  "p_number": "1234567890",
  "cvr": "24256790",
  "name": "Novo Nordisk - Bagsværd",
  "address": "Novo Allé 1",
  "zipcode": "2880",
  "city": "Bagsværd",
  "industry_code": "212000",
  "industry_text": "Fremstilling af farmaceutiske præparater",
  "employees": "42",
  "meta": { ... }
}

Referencekataloger

Industrikatalog

Brug /api/v1/industries til kundesync, når du vil spejle hele branchetræet i dit CRM, ERP eller lead-system uden først at lave tunge virksomhed-opslag.

Endpointet returnerer hele hierarkiet, ikke kun de laveste koder. Hver post er en node i træet med både parent_code og child_count, så du kan synkronisere hele strukturens relationer.

Labels på ikke-leaf noder er afledte opsummeringer af de underliggende brancher i snapshotet. De skal derfor læses som sync-labels og ikke som officielle DB07-overskrifter.

Snapshot freshness: meta.refreshed_at fortæller, hvornår snapshot blev opdateret. Snapshotet refreshes periodisk, så tællinger vil ofte være omkring et døgn gamle. Brug derfor altid tidsstemplet som den faktiske friskhedsindikator.

Eksempelbash
curl "https://firmaapi.dk/api/v1/industries"   -H "Authorization: Bearer cvr_live_..."
ResponseJSON
{
  "items": [
    {
      "code": "43",
      "display_code": "43",
      "label": "Forberedende byggepladsarbejder, Nedrivning, Testboring og prøvetagning m.fl.",
      "level": "division",
      "parent_code": null,
      "company_count": 1234,
      "primary_company_count": 1020,
      "secondary_company_count": 214,
      "child_count": 1
    },
    {
      "code": "431",
      "display_code": "43.1",
      "label": "Forberedende byggepladsarbejder, Nedrivning, Testboring og prøvetagning m.fl.",
      "level": "group",
      "parent_code": "43",
      "company_count": 420,
      "primary_company_count": 390,
      "secondary_company_count": 30,
      "child_count": 3
    }
  ],
  "meta": {
    "total": 123,
    "refreshed_at": "2026-03-28T10:00:00.000Z"
  }
}

Kommunekatalog

Brug /api/v1/municipalities til customer sync / reference use, når du vil spejle de kommunekoder og navne, som FirmaAPI eksponerer, i dit eget system uden tunge company-opslag først.

Kataloget er et lille statisk reference-sæt. Hent det én gang, gem mappingen mellem numeric_code og municipality_code lokalt, og brug derefter kommunenavnet som displayværdi i dit CRM, ERP eller lead-system.

Company-payloads inkluderer også municipality_name ved siden af municipality_code som et convenience-felt, så du kan vise adresser uden ekstra opslag.

Bemærk: Kommunekataloget er ikke et snapshot-lag med freshness claims eller cron-opdateringer. Det er en kanonisk reference-liste over de kommunekoder og navne, FirmaAPI eksponerer.

Eksempelbash
curl "https://firmaapi.dk/api/v1/municipalities"   -H "Authorization: Bearer cvr_live_..."
ResponseJSON
{
  "items": [
    { "code": "0101", "numeric_code": 101, "name": "København" },
    { "code": "0147", "numeric_code": 147, "name": "Frederiksberg" }
  ],
  "meta": {
    "total": 99
  }
}

Teknisk reference

Response-format

Alle responses returneres som JSON med UTF-8 encoding. Succesfulde kald returnerer data direkte, mens fejl returnerer et standardiseret fejlobjekt.

Virksomhedsobjekt

Felter i et standard virksomhedsresponse:

FeltTypeBeskrivelse
cvrstring8-cifret CVR-nummer
namestringOfficielt virksomhedsnavn
addressstringVejnavn og husnummer
zipcodestringPostnummer
citystringBy
municipality_codeinteger|nullKommunekode til brug med kommunekataloget
municipality_namestring|nullKommunenavn som convenience field
statusstringAktiv, Ophørt, Under konkurs m.fl.
company_typestring|nullVirksomhedsform (ApS, A/S, IVS m.fl.)
industry_codestring|null6-cifret branchekode
industry_textstring|nullBranchebeskrivelse
foundedstring|nullStiftelsesdato (YYYY-MM-DD)
employeesstring|nullAntal ansatte – eksakt tal som string eller normaliseret interval som 10-19 / 1000+
phonestring|nullTelefonnummer
emailstring|nullEmail
websitestring|nullHjemmeside
ad_protectedbooleanOm virksomheden er reklamebeskyttet
secondary_industriesarraySekundære brancher med code og text
capitalnumber|nullSelskabskapital (i mindste valutaenhed)
capital_currencystring|nullValuta for kapital (typisk DKK)
signing_rulestring|nullTegningsregel
production_unit_countnumberAntal produktionsenheder
purposestring|nullVirksomhedens formål
secondary_namesstring[]Aktive binavne
fiscal_year_endstring|nullRegnskabsårsslut (MM-DD)
accounting_classstring|nullRegnskabsklasse (A/B/C/D)
annual_employeesstring|nullÅrlig beskæftigelse (FTE-interval)
ownersarrayAktive ejere og ledelse med navn og rolle (title). Kun Basis plan eller højere.

Ekstra felter Standard+

Disse felter er kun tilgængelige med Standard plan eller højere:

FeltTypeBeskrivelse
employer_registration_datestring|nullFørste arbejdsgiverregistrering (YYYY-MM-DD)
vat_registration_datestring|nullFørste momsregistrering (YYYY-MM-DD)
import_registeredbooleanAktiv importregistrering
export_registeredbooleanAktiv eksportregistrering
excise_registrationsstring[]Aktive punktafgiftsregistreringer
first_fiscal_period_endstring|nullFørste regnskabsperiode slutdato

Ekstra felter Professionel+

Disse felter er kun tilgængelige med Professionel plan eller højere:

FeltTypeBeskrivelse
latitudenumber|nullBreddegrad (WGS84)
longitudenumber|nullLængdegrad (WGS84)
auditorsarrayRevisorer med name, title, participant_cvr og joined_date
all_emailsstring[]Alle email-adresser
all_phonesstring[]Alle telefonnumre
faxstring|nullFaxnummer
audit_formstring|nullRevisionsform
fiscal_year_startstring|nullRegnskabsår start (f.eks. "01-01")
secondary_industry_historyarrayHistorik over bibrancher inkl. inaktive

Reklamebeskyttelse

Virksomheder kan være reklamebeskyttede i CVR. Når ad_protected er true, returneres kontaktoplysninger (email, telefon, website) kun i autentificerede API-kald. Du må ikke bruge disse oplysninger til direkte markedsføring, herunder uopfordrede henvendelser via email, telefon, SMS eller fysisk post. Det er dit ansvar at overholde reklamebeskyttelsen.

Meta-objekt

Alle responses inkluderer et meta objekt med information om data-kilden:

JSON
"meta": {
  "cached_at": "2025-01-15T12:00:00.000Z",
  "source": "FirmaAPI"
}

Rate limits

API'et har rate limits per sekund og månedlige kvoter afhængig af din plan.

PlanRequests/sekOpslag/måned
Gratis21.000
Basis550.000
Standard10150.000
Professionel20500.000
EnterpriseIndividueltEfter aftale

Rate limit headers

Alle responses inkluderer rate limit headers, så du kan håndtere limits proaktivt:

X-RateLimit-Limit: 2          # Max requests per sekund for din plan
X-RateLimit-Remaining: 1      # Resterende requests i dette sekund
X-RateLimit-Reset: 1710000    # Unix timestamp for næste reset

Når du rammer grænsen

Hvis du overskrider din rate limit, returnerer API'et HTTP 429 Too Many Requests med en Retry-After header der angiver hvor mange sekunder du skal vente.

429 ResponseJSON
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Slow down.",
    "retry_after": 1
  }
}

Best practices

  • Spred dine requests jævnt ud – undgå at sende mange på én gang i bursts
  • Brug X-RateLimit-Remaining headeren til at bremse inden du rammer grænsen
  • Ved 429 – vent det antal sekunder Retry-After headeren angiver
  • Implementer exponential backoff: fordobl ventetiden ved gentagne 429-svar
  • Brug batch-endpointet (/api/v1/company/batch) i stedet for mange enkelte opslag

Eksempel med retry-logik (JavaScript):

async function fetchCompany(cvr, apiKey, retries = 3) {
  for (let i = 0; i < retries; i++) {
    const res = await fetch(`https://firmaapi.dk/api/v1/company/${cvr}`, {
      headers: { Authorization: `Bearer ${apiKey}` },
    });

    if (res.ok) return res.json();

    if (res.status === 429) {
      const wait = parseInt(res.headers.get("Retry-After") || "1");
      await new Promise((r) => setTimeout(r, wait * 1000 * (i + 1)));
      continue;
    }

    throw new Error(`API error: ${res.status}`);
  }
  throw new Error("Max retries exceeded");
}

Fejlhåndtering

Alle fejl returneres med en standardiseret JSON-struktur og passende HTTP statuskode.

Fejl-formatJSON
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Menneskelæselig beskrivelse af fejlen"
  }
}
HTTPKodeBeskrivelse
400INVALID_CVRUgyldigt CVR-format
400INVALID_P_NUMBERUgyldigt P-nummer format
400INVALID_QUERYUgyldig eller manglende søgeparameter
401MISSING_API_KEYIngen API-nøgle angivet
401INVALID_API_KEYUgyldig eller deaktiveret nøgle
403FORBIDDENAdgang nægtet
403PLAN_UPGRADE_REQUIREDFunktionen kræver en højere plan
403PLAN_NOT_SUPPORTEDEndpoint kræver højere plan
404NOT_FOUNDVirksomhed ikke fundet
429RATE_LIMIT_EXCEEDEDFor mange requests per sekund
429MONTHLY_LIMIT_EXCEEDEDMånedlig kvote opbrugt
500SERVER_ERRORIntern serverfejl
400INVALID_REQUESTUgyldigt request format
400TOO_MANYFor mange CVR-numre i batch-opslag
400INVALID_URLUgyldig webhook URL
403PLAN_NOT_ALLOWEDWebhooks er ikke tilgængelige på din plan
403EVENT_NOT_ALLOWEDHændelsestype ikke tilgængelig på din plan
403FILTERS_NOT_ALLOWEDWebhook-filtre kræver Professionel plan
502ERST_ERRORFejl ved opslag hos datakilden
503MAINTENANCEAPI er under vedligeholdelse

AI-integration (MCP)

Brug danske virksomhedsdata direkte i AI-assistenter som Claude Desktop, Claude Code, Cursor og andre MCP-kompatible klienter. Vores MCP-server giver din AI mulighed for at slå virksomheder op, søge og analysere regnskaber.

1. Installer pakken

Kør med din foretrukne package manager:

bash
npm install -g firmaapi-mcp-server    # npm
bun add -g firmaapi-mcp-server        # bun
pnpm add -g firmaapi-mcp-server       # pnpm

2. Konfigurer din AI-klient

Claude Desktop

Tilføj til din claude_desktop_config.json:

JSON
{
  "mcpServers": {
    "firmaapi": {
      "command": "firmaapi-mcp-server",
      "env": {
        "FIRMAAPI_KEY": "cvr_live_din_nøgle_her"
      }
    }
  }
}

Claude Code

bash
claude mcp add firmaapi -e FIRMAAPI_KEY=cvr_live_... -- firmaapi-mcp-server

Cursor

Tilføj til .cursor/mcp.json i dit projekt:

JSON
{
  "mcpServers": {
    "firmaapi": {
      "command": "firmaapi-mcp-server",
      "env": {
        "FIRMAAPI_KEY": "cvr_live_din_nøgle_her"
      }
    }
  }
}

3. Brug det

Bed bare din AI om at slå virksomheder op:

>

"Slå Novo Nordisk op og vis seneste regnskab"

>

"Find alle IT-virksomheder i Aarhus med over 50 ansatte"

>

"Hvem ejer CVR 34824770?"

>

"Søg efter Lars Larsen og vis hans virksomhedstilknytninger"

Tilgængelige MCP-tools (9 stk.)

ParameterTypeBeskrivelse
firmaapi_lookup_companytoolSlå virksomhed op via CVR-nummer med valgfrie ekstrablokke (finance, history, events, production_units, relations).
firmaapi_search_companiestoolSøg virksomheder på navn eller nøgleord.
firmaapi_advanced_searchtoolAvanceret søgning med filtre: by, branche, omsætning, ansatte, status m.m.
firmaapi_batch_lookuptoolBatch-opslag af flere CVR-numre på én gang (maks 100). Kræver Basis+.
firmaapi_get_persontoolHent personprofil med alle virksomhedstilknytninger via UUID (Professionel+).
firmaapi_search_personstoolSøg personprofiler på navn (Professionel+).
firmaapi_production_unittoolSlå produktionsenhed op via P-nummer (Standard+).
firmaapi_industriestoolHent branchekoder (DB07) med hierarki og antal virksomheder.
firmaapi_municipalitiestoolHent kommuneliste med koder til avanceret søgning.

Pakken på npm: npmjs.com/package/firmaapi-mcp-server — MCP-serveren bruger din eksisterende API-nøgle og tæller mod din plans kvote.

Webhooks

Modtag automatisk besked når virksomhedsdata ændrer sig. Webhooks sender HTTP POST requests til dit endpoint med signerede payloads.

Tilgængelig fra Standard-planen. Se priser for detaljer.

Webhook-mønstre: enkelt-match og batch

Webhooks er event-drevne. Du kan bruge samme endpoint til både enkelt-match og batch-flow afhængigt af din egen routinglogik.

  • Enkelt-match: filtrér i din modtager på data.company.cvr og send videre til en dedikeret job-kø for det enkelte CVR.
  • Batch: saml events i vinduer (fx 1-5 min), dedupliker på CVR og kør batch-opslag mod /api/v1/company/batch.
Batch-opslag fra webhook-eventsbash
curl -X POST https://firmaapi.dk/api/v1/company/batch   -H "Authorization: Bearer cvr_live_..."   -H "Content-Type: application/json"   -d '{"cvr_numbers": ["24256790", "19625095", "10940834"]}'

Event-typer

EventBeskrivelsePlan
company.status.changedVirksomhedens status er ændretStandard+
company.bankruptcyKonkurs eller tvangsopløsningStandard+
company.createdNy virksomhed registreretProfessionel
company.name.changedNavneændringProfessionel
company.address.changedAdresseændringProfessionel
company.participant.changedEjere/ledelse ændretProfessionel
company.financials.publishedNyt årsregnskab offentliggjortProfessionel
company.industry.changedBranchekode ændretProfessionel
company.type.changedVirksomhedsform ændretProfessionel
company.capital.changedSelskabskapital ændretProfessionel

Opret webhook

POST/api/v1/webhooks
Request
curl -X POST https://firmaapi.dk/api/v1/webhooks \
  -H "Authorization: Bearer din_api_nøgle" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://din-server.dk/webhook",
    "event_types": ["company.status.changed", "company.bankruptcy"],
    "description": "Status-overvågning"
  }'

Webhook-secret returneres kun ved oprettelse. Gem det sikkert – det bruges til at verificere signaturer.

Payload-format

Webhook payload
{
  "id": "evt_abc123",
  "type": "company.status.changed",
  "created_at": "2026-03-15T10:30:00.000Z",
  "data": {
    "company": {
      "cvr": "12345678",
      "name": "Firma A/S",
      "status": "Under konkurs",
      ...
    },
    "changes": {
      "status": { "old": "Aktiv", "new": "Under konkurs" }
    }
  }
}

Signaturverifikation

Alle webhook-requests signeres med HMAC-SHA256. Verificér signaturen for at sikre at requesten kommer fra FirmaAPI.

Headers: X-FirmaAPI-Signature, X-FirmaAPI-Timestamp, X-FirmaAPI-Event, X-FirmaAPI-Delivery

Node.js – verificér signatur
const crypto = require("crypto");

function verifySignature(secret, timestamp, body, signature) {
  const payload = timestamp + "." + body;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// I din webhook handler:
app.post("/webhook", (req, res) => {
  const sig = req.headers["x-firmaapi-signature"];
  const ts = req.headers["x-firmaapi-timestamp"];
  const body = JSON.stringify(req.body);

  if (!verifySignature(WEBHOOK_SECRET, ts, body, sig)) {
    return res.status(401).send("Invalid signature");
  }

  // Håndtér event
  console.log(req.body.type, req.body.data);
  res.status(200).send("OK");
});
Python – verificér signatur
import hmac, hashlib

def verify_signature(secret, timestamp, body, signature):
    payload = f"{timestamp}.{body}"
    expected = hmac.new(
        secret.encode(), payload.encode(), hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

# I din webhook handler (Flask):
@app.route("/webhook", methods=["POST"])
def webhook():
    sig = request.headers.get("X-FirmaAPI-Signature")
    ts = request.headers.get("X-FirmaAPI-Timestamp")
    body = request.get_data(as_text=True)

    if not verify_signature(WEBHOOK_SECRET, ts, body, sig):
        return "Invalid signature", 401

    event = request.get_json()
    print(event["type"], event["data"])
    return "OK", 200

Retry-politik

Hvis dit endpoint returnerer en ikke-2xx status, forsøger vi igen med eksponentiel backoff:

ForsøgVentetid
1. retry1 minut
2. retry5 minutter
3. retry30 minutter
4. retry2 timer

Efter 5 consecutive failures deaktiveres webhooket automatisk. Du kan genaktivere det via API.

Filtre (Professionel)

Med Professionel-planen kan du filtrere events baseret på branchekode og kommunekode:

Webhook med filtre
{
  "url": "https://din-server.dk/webhook",
  "event_types": ["company.status.changed"],
  "filters": {
    "industry_codes": ["620100", "620200"],
    "municipality_codes": [101, 147]
  }
}

API-endpoints

MetodeEndpointBeskrivelse
POST/api/v1/webhooksOpret webhook
GET/api/v1/webhooksList webhooks
GET/api/v1/webhooks/:idWebhook detaljer
PATCH/api/v1/webhooks/:idOpdater webhook
DELETE/api/v1/webhooks/:idSlet webhook
GET/api/v1/webhooks/:id/deliveriesDelivery log
POST/api/v1/webhooks/:id/testSend test-event

Kodeeksempler

Komplet eksempler i populære sprog. Kopier og tilpas til dit projekt.

JavaScript / Node.js

javascript
// Enkelt CVR-opslag
const response = await fetch("https://firmaapi.dk/api/v1/company/24256790", {
  headers: { Authorization: "Bearer cvr_live_din_nøgle" }
});
const company = await response.json();
console.log(company.name); // "Novo Nordisk A/S"

// Søgning
const searchRes = await fetch(
  "https://firmaapi.dk/api/v1/company/search?q=novo+nordisk&limit=5",
  { headers: { Authorization: "Bearer cvr_live_din_nøgle" } }
);
const { results } = await searchRes.json();
results.forEach(c => console.log(c.cvr, c.name));

// Batch-opslag
const batchRes = await fetch("https://firmaapi.dk/api/v1/company/batch", {
  method: "POST",
  headers: {
    Authorization: "Bearer cvr_live_din_nøgle",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ cvr_numbers: ["24256790", "54562519"] })
});
const batch = await batchRes.json();
console.log(batch.results.length, "virksomheder hentet");

Python

python
import requests

API_KEY = "cvr_live_din_nøgle"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

# Enkelt CVR-opslag
r = requests.get(
    "https://firmaapi.dk/api/v1/company/24256790",
    headers=HEADERS
)
company = r.json()
print(company["name"])  # "Novo Nordisk A/S"

# Søgning
r = requests.get(
    "https://firmaapi.dk/api/v1/company/search",
    params={"q": "novo nordisk", "limit": 5},
    headers=HEADERS
)
for c in r.json()["results"]:
    print(c["cvr"], c["name"])

# Batch-opslag
r = requests.post(
    "https://firmaapi.dk/api/v1/company/batch",
    json={"cvr_numbers": ["24256790", "54562519"]},
    headers=HEADERS
)
print(f"{r.json()['total']} virksomheder hentet")

PHP

php
<?php
$apiKey = "cvr_live_din_nøgle";

// Enkelt CVR-opslag
$ch = curl_init("https://firmaapi.dk/api/v1/company/24256790");
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ["Authorization: Bearer $apiKey"],
    CURLOPT_RETURNTRANSFER => true,
]);
$company = json_decode(curl_exec($ch), true);
echo $company["name"]; // "Novo Nordisk A/S"

// Batch-opslag
$ch = curl_init("https://firmaapi.dk/api/v1/company/batch");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer $apiKey",
        "Content-Type: application/json"
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "cvr_numbers" => ["24256790", "54562519"]
    ]),
    CURLOPT_RETURNTRANSFER => true,
]);
$batch = json_decode(curl_exec($ch), true);
echo count($batch["results"]) . " virksomheder hentet";

C# / .NET

csharp
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer cvr_live_din_nøgle");

// Enkelt CVR-opslag
var response = await client.GetAsync("https://firmaapi.dk/api/v1/company/24256790");
var json = await response.Content.ReadAsStringAsync();
var company = JsonSerializer.Deserialize<JsonElement>(json);
Console.WriteLine(company.GetProperty("name").GetString());

// Batch-opslag
var batchBody = new StringContent(
    JsonSerializer.Serialize(new { cvr_numbers = new[] { "24256790", "54562519" } }),
    Encoding.UTF8,
    "application/json"
);
var batchRes = await client.PostAsync("https://firmaapi.dk/api/v1/company/batch", batchBody);
var batchJson = await batchRes.Content.ReadAsStringAsync();

Go

go
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "io"
)

func main() {
    req, _ := http.NewRequest("GET",
        "https://firmaapi.dk/api/v1/company/24256790", nil)
    req.Header.Set("Authorization", "Bearer cvr_live_din_nøgle")

    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    var company map[string]interface{}
    json.Unmarshal(body, &company)
    fmt.Println(company["name"]) // "Novo Nordisk A/S"
}

Gratis API-nøgle på 30 sekunder

1.000 opslag/md. Ingen kreditkort.