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,events" \
  -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 }
    ]
  },
  "executives": [
    { "name": "Lars Fruergaard Jørgensen", "function": "ADM. DIR." }
  ],
  "board_members": [
    { "name": "Helge Lund", "function": "BESTYRELSESFORMAND", "election_method": "GENERALFORSAMLING" }
  ],
  "meta": {
    "cached_at": "2025-01-15T12:00:00Z",
    "source": "FirmaAPI"
  }
}

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, produktionsenheder, relationer og hændelser.

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 og ansatte. Basis+ får ejerregister-poster; Standard+ får ekstra beskæftigelse; Professionel+ får kontakt- og faxhistorik.
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. Standard får p_number/name; Professionel+ får adresse, branche og ansatte.
Standard+
relations
Koncernrelationer
Henter direkte moder-/datterselskabsrelationer som en ekstra blok. Relationer udledes fra ejerregisteret og legacy owners-data.
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
history.register_entries til legale ejere og executives/board_members til ledelse.
history.register_entriesexecutivesboard_members
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 og regnskabsårsstart følger automatisk med.
audit_formfiscal_year_start
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, børs-/revisionsflag og årlig beskæftigelse følger automatisk med.
purposesecondary_namesfiscal_year_endaccounting_classis_listedaudit_opted_outannual_employees
Alle planer
ERST-attributter (Standard+)
Arbejdsgiver-/momsregistrering, import/eksport-status, punktafgifter og første regnskabsperiode.
employer_registration_datevat_registration_dateimport_registeredexport_registeredexcise_registrationsfirst_fiscal_period_endfirst_fiscal_period_start
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
}

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.

Datakilde: Batch-opslag læser fra FirmaAPI's cache/database for at holde svartiden stabil. Hvis et CVR-nummer endnu ikke er synkroniseret, returneres det i errors. Brug single-opslag /api/v1/company/{cvr} når du vil tvinge et live fallback-opslag mod ERST.

Ejere og ledelse

Brug history.register_entries til legale ejere fra ejerregistret og executives/board_members til ledelse.

Plan-adgang: Gratis = ingen adgang. Basis+ får history.register_entries, executives og board_members. Professionel+ tilføjer person_id, personprofiler, ejerskabshistorik og udvidede governance-felter.

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": {
    "register_entries": [{
      "name": "Novo Holdings A/S",
      "ownership_percentage": 0.281,
      "voting_percentage": 0.765,
      "valid_from": "1999-01-01",
      "valid_to": null
    }]
  },
  "executives": [
    {
      "name": "Lars Fruergaard Jørgensen",
      "function": "ADM. DIR.",
      "joined_date": "2017-01-01",
      "left_date": null
    }
  ],
  "board_members": [
    {
      "name": "Helge Lund",
      "function": "BESTYRELSESFORMAND",
      "election_method": "GENERALFORSAMLING"
    }
  ]
}

Basis+: history.register_entries viser legale ejere fra ejerregistret med ejerandel og stemmeret. executives og board_members viser ledelse som separate felter. Professionel+: tilføjer person_id, personprofiler, ownership_history og udvidede governance-felter.

Deltager-CVR: participant_cvr er det bedste kendte CVR for en selskabsdeltager. Hvis ERST kun sender participant_enhedsnummer, forsøger FirmaAPI at udfylde CVR fra den lokale virksomheds-cache. Feltet kan stadig være null for personer, udenlandske enheder eller deltagere der endnu ikke kan resolves.

Personprofil-link: person_id er Professionel+ og 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å history.register_entries, auditors, ownership_history, executives og board_members for Professionel/Enterprise.

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 history.register_entries, auditors, ownership_history, executives og board_members 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å history.register_entries/auditors/ownership_history/executives/board_members 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",
      "company_status": "Aktiv",
      "company_status_kind": "active",
      "is_active": true,
      "role_title": "Bestyrelsesmedlem",
      "joined_date": "2023-01-01",
      "left_date": null,
      "duration_days": 1234
    }
  ]
}

Aktive vs historiske roller: Brug affiliations[].is_active som autoritativ status. Feltet er kun true, når rollen er åben, og virksomheden ikke er lukket. Ved afsluttet virksomhed sættes left_date senest til den kendte ophørsdato; en tidligere fratrædelsesdato bevares. For en virksomhed opløst efter konkurs bruges opløsningsdatoen, ikke konkursens begyndelse. Mangler en sikker ophørsdato, forbliver datoen null, men rollen er stadig historisk. De samme aktivitetsregler bruges i active_company_count og historical_company_count.

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. Basis+ får også ejerregisteret, Standard+ får kvartals-/månedsbeskæftigelse, og Professionel+ får kontakt- og faxhistorik.

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 }
    ],
    "emails": [
      { "value": "[email protected]", "valid_from": "2020-01-01", "valid_to": null, "ad_protected": false }
    ],
    "phones": [
      { "value": "44448888", "valid_from": "2020-01-01", "valid_to": null, "ad_protected": false }
    ],
    "faxes": [
      { "value": "44448889", "valid_from": "2010-01-01", "valid_to": "2020-01-01", "ad_protected": false }
    ],
    "websites": [
      { "value": "https://www.novonordisk.com", "valid_from": "2020-01-01", "valid_to": null, "ad_protected": false }
    ]
  }
}
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 med p_number og name. Professionel/Enterprise = udvidede P-enhedsfelter som adresse, branche og ansatte.

Eksempelbash
curl "https://firmaapi.dk/api/v1/company/24256790?include=production_units"   -H "Authorization: Bearer cvr_live_..."
Response (Professionel+)JSON
{
  "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"
  }
}

Netto-vækst (analytics)

Aggregér stiftelser og opløsninger pr. tidsbucket og se nettoudviklingen for et segment. Bruger samme filtre som /search/advanced (industry, municipality, city, employees osv.). Status-filteret ignoreres bevidst, fordi opløsningstællingen pr. definition er 0 for status=Aktiv. Mindst ét filter er påkrævet.

GET/api/v1/search/net-growth
ParameterTypeBeskrivelse
granularitystringmonth | quarter | year (default: year)
fromstringTidligste dato (YYYY-MM-DD)
tostringSeneste dato (YYYY-MM-DD)
industrystringBranchekode, prefix-match (fx 62 = IT-tjenester)
municipalitystringKommune-navn eller kode
citystringBy
employeesstringFx 5+, 2-10, 42 eller ANTAL_10_19
Eksempel — netto-vækst i IT-branchen pr. årbash
curl "https://firmaapi.dk/api/v1/search/net-growth?industry=62&granularity=year&from=2020-01-01"   -H "Authorization: Bearer cvr_live_..."
ResponseJSON
{
  "granularity": "year",
  "from": "2020-01-01",
  "to": null,
  "bucket_count": 6,
  "truncated": false,
  "totals": {
    "founded": 12450,
    "dissolved": 6810,
    "net_growth": 5640
  },
  "results": [
    { "bucket": "2020", "founded": 1820, "dissolved": 980, "net_growth": 840 },
    { "bucket": "2021", "founded": 2140, "dissolved": 1050, "net_growth": 1090 },
    { "bucket": "2022", "founded": 2380, "dissolved": 1190, "net_growth": 1190 },
    { "bucket": "2023", "founded": 2210, "dissolved": 1240, "net_growth": 970 },
    { "bucket": "2024", "founded": 2090, "dissolved": 1310, "net_growth": 780 },
    { "bucket": "2025", "founded": 1810, "dissolved": 1040, "net_growth": 770 }
  ]
}

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.

Kataloget bygger på de branchekoder og betegnelser, vi har modtaget fra CVR. En kode angiver ikke i sig selv, om den tilhører DB07 eller DB25. Vi omkoder ikke historiske registreringer automatisk. Labels på overordnede noder er afledte opsummeringer af de underliggende brancher og er ikke officielle DB07- eller DB25-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
  }
}

Regionskatalog

Brug /api/v1/regions til at slå de 5 danske regioner op med fuld kommune-medlemskab. Kataloget bruges som lookup for region- og region_list-filtrene på /api/v1/search/advanced.

Kilde: Danmarks Statistik / regionsinddelingen. Bornholm og Christiansø-enklaven hører under Region Hovedstaden.

Bemærk: Regionskataloget er statisk reference-data uden freshness-claims. Hent én gang og gem lokalt.

Eksempelbash
curl "https://firmaapi.dk/api/v1/regions"   -H "Authorization: Bearer cvr_live_..."
ResponseJSON
{
  "items": [
    {
      "name": "Hovedstaden",
      "full_name": "Region Hovedstaden",
      "municipality_count": 30,
      "municipalities": [
        { "numeric_code": 101, "name": "København" },
        { "numeric_code": 147, "name": "Frederiksberg" }
      ]
    }
  ],
  "meta": {
    "total": 5,
    "total_municipalities": 99,
    "source": "Danmarks Statistik / regionsinddelingen (5 regioner)"
  }
}

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
is_listedboolean|nullBørsnoteret virksomhed
audit_opted_outboolean|nullRevision fravalgt
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)

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
first_fiscal_period_startstring|nullFørste regnskabsperiode startdato
address_fullobject|nullFuld struktureret adresse med floor, door, po_box, country_code m.fl.
articles_last_amendedstring|nullSeneste vedtægtsændring (ISO-dato)
dissolution_threatobject|nullSeneste opløsningstrussel ({date, reason})
subject_to_aml_lawboolean|nullOmfattet af lov om hvidvask og terrorfinansiering
has_share_classesboolean|nullHar virksomheden flere aktieklasser
archive_registration_numberstring|nullArkivregistreringsnummer
name_identitystring|nullNavn-identitet (NAVN_IDENTITET fra ERST)
has_pseudo_cvrboolean|nullPseudo-CVR-flag (har virksomheden et pseudo-CVR i CVR-registeret)

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
history.faxesarrayTelefax-historik via include=history
audit_formstring|nullRevisionsform
fiscal_year_startstring|nullRegnskabsår start (f.eks. "01-01")
secondary_industry_historyarrayHistorik over bibrancher inkl. inaktive
executivesarrayDirektion/direktører (DIREKTION, DIREKTØR, ADM. DIR.). Basis+; Professionel/Enterprise inkluderer person_id når personen er matchet.
board_membersarrayBestyrelse + formænd inkl. valgform (VALGFORM). Basis+; Professionel/Enterprise inkluderer person_id når personen er matchet.
alternatesarraySuppleanter — alternate_for_enhedsnummer peger på primær
liquidatorsarrayLikvidatorer (LIKVIDATOR-funktion i LEDELSESORGAN)
sustainability_auditorsarrayBæredygtighedsrevisorer (BÆREDYGTIGHEDSREVISION)
unlimited_liability_partnersarrayFuldt ansvarlige deltagere (I/S, K/S, enkeltmandsvirksomhed)
foundersarrayStiftere (STIFTERE-organisation)
register_entriesarrayEjerregister-poster med ejerandel og stemmeret (Basis+, via include=history; person_id kun Professionel+)
auditor_officesarrayRevisorers kontorsteder (kontorsted-adresse)

Reklamebeskyttelse

Virksomheder kan være reklamebeskyttede i CVR. Feltet ad_protected markerer, at kontaktoplysninger ikke må bruges til direkte markedsføring, herunder uopfordrede henvendelser via email, telefon, SMS eller fysisk post. Alle FirmaAPI-kald er autentificerede, men det er stadig 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",
  "data_age_seconds": 3600,
  "source": "FirmaAPI",
  "provenance": {
    "provider": "erst",
    "source": "postgres",
    "resolution_rule": "db_row"
  }
}

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.

Hurtig opsætning

Lad din AI sætte serveren op

Har du en AI-klient med filadgang (f.eks. Claude Code, Claude Desktop med værktøjer, Cursor), kan du copy-paste prompten nedenfor og få den til at installere pakken og tilføje konfigurationen for dig. Prompten indeholder ikke din API-nøgle — AI'en bruger placeholderen DIN_API_NØGLE_HER, og fortæller dig til sidst præcis hvilken fil (inkl. sti) og hvilken linje du selv skal indsætte din nøgle i.

Prompt til din AI
Installer og konfigurer firmaapi-mcp-server for mig.

1. Installer pakken globalt: npm install -g firmaapi-mcp-server
2. Find ud af hvilken AI-klient jeg bruger (Claude Desktop, Claude Code,
   Cursor eller lignende) og mit styresystem.
3. Opret eller rediger den korrekte konfigurationsfil, og tilføj firmaapi
   som MCP-server med kommandoen "firmaapi-mcp-server" og env-variablen
   FIRMAAPI_KEY sat til strengen "DIN_API_NØGLE_HER" (lad placeholderen
   stå — jeg udskifter den selv bagefter).
4. Fortæl mig til sidst:
   - Den fulde sti til den fil du redigerede.
   - Hvilken linje i filen jeg skal åbne og udskifte "DIN_API_NØGLE_HER"
     med min rigtige nøgle.
   - Hvordan jeg genstarter klienten så serveren bliver indlæst.

Skriv IKKE min rigtige API-nøgle nogen steder — brug kun placeholderen.

Foretrækker du at sætte det op selv? Følg trinnene herunder.

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 (33 stk.)

Opslag og søgning

ParameterTypeBeskrivelse
firmaapi_lookup_companyGratisSlå virksomhed op via CVR-nummer med valgfrie ekstrablokke (finance, history, events, production_units, relations).
firmaapi_search_companiesGratisHurtig navnesøgning med fuzzy match.
firmaapi_advanced_searchGratisAvanceret søgning med 30+ filtre (by, branche, omsætning, ansatte, region, alder, regnskabsklasse m.m.).
firmaapi_batch_lookupBasisSlå op til 500 CVR-numre op i én forespørgsel.
firmaapi_validate_cvrGratisModulus-11-tjek og valgfri verificering af om CVR'et faktisk findes.

Personer og relationer

ParameterTypeBeskrivelse
firmaapi_get_personProHent personprofil med alle virksomhedstilknytninger via UUID.
firmaapi_search_personsProSøg personprofiler på navn, alias eller enhedsnummer.
firmaapi_my_portfolioProFå en persons fulde portefølje af aktive og historiske roller.
firmaapi_person_rolesProRegistrerede virksomhedstilknytninger og perioder uden vurdering.

Analyse og aggregering

ParameterTypeBeskrivelse
firmaapi_aggregateGratisAggreger virksomheder pr. branche, kommune, selskabsform, stiftelsesår m.fl.
firmaapi_timeseriesGratisStiftelser og opløsninger over tid, opdelt pr. dag, uge, måned eller år.
firmaapi_net_growthGratisNettovækst (stiftelser minus opløsninger) pr. tidsperiode.
firmaapi_industry_landscapeStandardDe største virksomheder i en branche eller region, rangordnet.
firmaapi_geographical_distributionGratisGeografisk fordeling af virksomheder pr. kommune eller region.

Finansiel analyse

ParameterTypeBeskrivelse
firmaapi_financial_metricsStandardRegnskabstal, perioder og nøgletal fra API'et uden score eller klassifikation.
firmaapi_growth_trajectoryStandardTilgængelige årsregnskaber og år-til-år-vækstrater uden klassifikation.
firmaapi_dividend_historyStandardSamlet udbetalt udbytte over alle år og udbyttegrad pr. år.
firmaapi_industry_benchmarkStandardSammenlign en virksomhed med konkurrenter i samme branche og region.

Virksomhedsoplysninger

ParameterTypeBeskrivelse
firmaapi_company_overviewStandardRegistreret status, ledelse og regnskaber uden samlet vurdering.
firmaapi_change_detectorGratisFind virksomheder med ændringer (status, navn, adresse, ledelse) siden en given dato.
firmaapi_company_structureProRegistreret formål, selskabsform, ansatte og relationer uden klassifikation.
firmaapi_dependency_analysisProVis registrerede selskabsforbindelser og fælles personer.

Ejerskab og netværk

ParameterTypeBeskrivelse
firmaapi_ownership_treeProEjerskabskæde op og ned, op til fire niveauer.
firmaapi_board_overlapProFind sammenfaldende ledelsesmedlemmer mellem to selskaber.
firmaapi_find_target_companiesBasisLeadgenerering med fornuftige defaults: aktive virksomheder med kontaktoplysninger, sorteret efter omsætning.
firmaapi_lookalike_searchBasisFind virksomheder der minder om en given CVR.
firmaapi_compare_companiesStandardSide-om-side sammenligning af 2-4 selskaber.

Reference og kataloger

ParameterTypeBeskrivelse
firmaapi_industriesGratisBranchekoder (DB07) i hierarki med antal virksomheder pr. kode.
firmaapi_municipalitiesGratisListe over danske kommuner med koder.
firmaapi_regionsGratisDe fem danske regioner med tilhørende kommuner.
firmaapi_production_unitStandardSlå produktionsenhed op via P-nummer.
firmaapi_explain_fieldGratisForklaring af 60+ CVR- og ERST-termer (statusværdier, roller, attributter).
firmaapi_explain_queryGratisTæl matches uden at hente data. Sanity-check søgning før tunge kald.

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
person.profile.createdNy erhvervsperson registreretProfessionel
person.affiliation.addedPerson fik ny rolle i en virksomhedProfessionel
person.affiliation.endedPerson udtrådte af en virksomhedProfessionel
person.role.changedPersons rolletitel ændret (fx direktør → bestyrelsesmedlem)Professionel
person.ownership.changedEjer- eller stemmeandel ændret for personProfessionel
person.profile.mergedTo personprofiler konsolideret (planlagt — abonnement muligt, men emitteres ikke endnu)Professionel

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

company.* 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" }
    }
  }
}

Person-events (person.*) følger samme konvolut, men data indeholder en person-reference, det CVR hvor rollen er forbundet, samt affiliation-detaljer. For role.changed og ownership.changed medsendes også et changes-objekt med old/new-deltaer.

person.role.changed payload
{
  "id": "evt_xyz789",
  "type": "person.role.changed",
  "created_at": "2026-04-24T08:15:00.000Z",
  "data": {
    "person": {
      "id": "7d64b6da-8d23-4a1a-8ca4-4e4f1d0c0a11",
      "slug": "jens-hansen",
      "canonical_name": "Jens Hansen"
    },
    "cvr": "12345678",
    "company_name": "Acme ApS",
    "affiliation": {
      "source_type": "ownership_history",
      "role_title": "Bestyrelsesmedlem",
      "joined_date": "2024-01-01",
      "left_date": null,
      "ownership_share": 60,
      "voting_share": 60,
      "confidence": 0.95
    },
    "changes": {
      "role_title": { "old": "Direktør", "new": "Bestyrelsesmedlem" },
      "ownership_share": { "old": 40, "new": 60 },
      "voting_share": { "old": 40, "new": 60 }
    }
  }
}

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

Hver levering forsøges højst 5 gange. Ved fejl holdes en stigende pause for det pågældende endpoint, mens andre endpoints fortsætter. Webhooket forbliver aktivt. Efter sidste fejl markeres leveringen som fejlet. Respektér hændelsens id for at undgå dobbeltbehandling ved genforsøg.

Filtre (Professionel)

Med Professionel-planen kan du filtrere events baseret på branchekode, kommunekode eller specifikke person-IDs (person_ids accepterer både UUID og slug, op til 500 entries).

Webhook med company-filtre
{
  "url": "https://din-server.dk/webhook",
  "event_types": ["company.status.changed"],
  "filters": {
    "industry_codes": ["620100", "620200"],
    "municipality_codes": [101, 147]
  }
}
Webhook der kun følger udvalgte personer
{
  "url": "https://din-server.dk/webhook",
  "event_types": [
    "person.affiliation.added",
    "person.affiliation.ended",
    "person.role.changed",
    "person.ownership.changed"
  ],
  "filters": {
    "person_ids": [
      "7d64b6da-8d23-4a1a-8ca4-4e4f1d0c0a11",
      "jens-hansen"
    ]
  },
  "description": "Overvågning af registrerede personroller"
}

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.