Gå til hovedindhold
FirmaAPIFirmaAPI

Regneark

CVR-opslag i Excel og Google Sheets

Slå en liste af CVR-numre op i dit regneark og få navn, adresse, status og branche tilbage. Det kræver lidt opsætning, fordi regnearkets indbyggede web-funktioner ikke kan sende en API-nøgle. Her er de løsninger, der virker.

Hvad der virker, og hvad der ikke gør

API'et kræver, at nøglen sendes i en HTTP-header. Det sætter en grænse for, hvilke regnearks-funktioner du kan bruge:

MetodeVirker?Bemærkning
Excel: Power Query (Web.Contents)JaKan sætte headers. Anbefalet i Excel
Excel: WEBSERVICE-funktionenNejKan ikke sende en Authorization-header
Google Sheets: Apps Script (UrlFetchApp)JaKan sætte headers. Anbefalet i Sheets
Google Sheets: IMPORTDATA og IMPORTXMLNejKan ikke sende headers
Ark med færdig formel uden opsætningNejDer er intet indbygget CVR-opslag

Hvis du bare skal have et par virksomheder ind en enkelt gang, er det hurtigere at kalde API'et fra en terminal eller et script og indsætte resultatet. Skal opslagene gentages, giver en forespørgsel eller et script mening.

Excel: ét opslag med Power Query

Power Query er en del af Excel under fanen Data. Vælg Hent data, Fra andre kilder og Tom forespørgsel, åbn Avanceret editor, og indsæt koden. Skift ApiKey ud med din egen nøgle. Første gang beder Excel om at vælge legitimationsoplysninger til adressen. Vælg Anonym, fordi nøglen sendes i headeren.

Power Query (M)

let
    ApiKey = "cvr_live_din_api_nøgle",
    Svar = Web.Contents(
        "https://firmaapi.dk/api/v1/company/24256790",
        [Headers = [Authorization = "Bearer " & ApiKey]]
    ),
    Data = Json.Document(Svar),
    Tabel = Record.ToTable(
        Record.SelectFields(Data, {"cvr", "name", "status", "address", "zipcode", "city", "industry_text", "employees"})
    )
in
    Tabel

Resultatet bliver en tabel med feltnavn og værdi for den ene virksomhed. Vil du have flere felter, tilføjer du flere navne i Record.SelectFields. Alle felter står i dokumentationen.

Excel: en liste af CVR-numre

Lav en tabel i dit ark med kolonnen CVR, og giv tabellen navnet Cvrliste. Forespørgslen herunder laver et opslag pr. række og udvider felterne som kolonner. Fejl, som 404, bliver til en tekst i kolonnen Status, så resten af listen stadig kommer med.

Power Query (M)

let
    ApiKey = "cvr_live_din_api_nøgle",
    Hent = (cvr as text) as record =>
        let
            Svar = Web.Contents(
                "https://firmaapi.dk/api/v1/company/" & cvr,
                [
                    Headers = [Authorization = "Bearer " & ApiKey],
                    ManualStatusHandling = {400, 401, 403, 404, 429}
                ]
            ),
            Status = Value.Metadata(Svar)[Response.Status],
            Tom = [name = null, status = "Fejl " & Text.From(Status), address = null, zipcode = null, city = null, industry_text = null, employees = null],
            Data = if Status = 200 then Json.Document(Svar) else Tom
        in
            Data,
    Kilde = Excel.CurrentWorkbook(){[Name = "Cvrliste"]}[Content],
    Typer = Table.TransformColumnTypes(Kilde, {{"CVR", type text}}),
    MedData = Table.AddColumn(Typer, "Data", each Hent([CVR])),
    Udvid = Table.ExpandRecordColumn(
        MedData, "Data",
        {"name", "status", "address", "zipcode", "city", "industry_text", "employees"}
    )
in
    Udvid
Hver række tæller som ét opslag, og Excel kører forespørgslen igen, hver gang du opdaterer. Sæt opdatering til manuel, og brug ikke automatisk opdatering ved åbning af filen, hvis du har mange rækker. Viser Excel en fejl om Formula.Firewall eller privatlivsniveauer, fordi data fra arket bruges i en webadresse, kan du i forespørgslens indstillinger ignorere privatlivsniveauer for projektmappen.

Excel: batch-opslag med POST

Har du Basis eller højere, kan du slå op til 100 CVR-numre op i ét kald. Det er hurtigere end ét opslag pr. række. Hvert fundet CVR-nummer tæller stadig som ét opslag. Når du angiver et Content i Web.Contents, sender Power Query en POST.

Power Query (M)

let
    ApiKey = "cvr_live_din_api_nøgle",
    Cvrer = {"24256790", "54562519", "25313763"},
    Svar = Web.Contents(
        "https://firmaapi.dk/api/v1/company/batch",
        [
            Headers = [Authorization = "Bearer " & ApiKey, #"Content-Type" = "application/json"],
            Content = Json.FromValue([cvr_numbers = Cvrer])
        ]
    ),
    Json = Json.Document(Svar),
    Tabel = Table.FromRecords(Json[results], {"cvr", "name", "status", "city", "employees"}, MissingField.UseNull)
in
    Tabel

Svaret har felterne results, errors, total og quota_used. CVR-numre, der ikke findes, ligger i errors. Over 100 numre må du dele listen op, hvis du er på Basis.

Google Sheets: en funktion med Apps Script

I Google Sheets åbner du Udvidelser og Apps Script, indsætter koden og gemmer nøglen under Projektindstillinger, Script Properties med navnet FIRMAAPI_KEY. Så kan du skrive =CVR(A2; "name") eller =CVR(A2; "city") direkte i arket. Afhængigt af dine regionale indstillinger bruges komma eller semikolon som skilletegn.

Apps Script: egen CVR-funktion

const BASE = "https://firmaapi.dk/api/v1";

function noegle_() {
  return PropertiesService.getScriptProperties().getProperty("FIRMAAPI_KEY");
}

/**
 * Slår en virksomhed op på CVR-nummer.
 * @param {string} cvr CVR-nummer
 * @param {string} felt Feltnavn, fx "name", "city" eller "status"
 * @customfunction
 */
function CVR(cvr, felt) {
  const nummer = String(cvr).replace(/\D/g, "");
  if (nummer.length !== 8) return "Ugyldigt CVR";

  const cache = CacheService.getScriptCache();
  let tekst = cache.get("cvr_" + nummer);

  if (!tekst) {
    const res = UrlFetchApp.fetch(BASE + "/company/" + nummer, {
      headers: { Authorization: "Bearer " + noegle_() },
      muteHttpExceptions: true,
    });
    const status = res.getResponseCode();
    if (status === 404) return "Ikke fundet";
    if (status === 429) return "For mange opslag";
    if (status !== 200) return "Fejl " + status;

    const d = JSON.parse(res.getContentText());
    // Cachen tager højst 100 KB pr. værdi, så gem kun de felter, vi bruger.
    const slank = {
      name: d.name, status: d.status, address: d.address, zipcode: d.zipcode,
      city: d.city, company_type: d.company_type, industry_text: d.industry_text,
      employees: d.employees, founded: d.founded, phone: d.phone,
    };
    tekst = JSON.stringify(slank);
    cache.put("cvr_" + nummer, tekst, 21600); // 6 timer
  }

  const data = JSON.parse(tekst);
  const vaerdi = data[felt || "name"];
  return vaerdi === undefined || vaerdi === null ? "" : vaerdi;
}

Funktionen gemmer svaret i seks timer, så du ikke bruger et nyt opslag, hver gang arket genberegner. Google begrænser både køretiden pr. funktionskald og antallet af eksterne kald pr. dag, så den passer bedst til få hundrede rækker ad gangen.

Google Sheets: opdatér en hel liste

Til længere lister er et script bedre end en funktion pr. celle. Scriptet herunder læser CVR-numre fra kolonne A, slår dem op i grupper på 100 med batch-endpointet og skriver navn, status, by og antal ansatte i kolonne B til E. Du kan koble det til en knap eller en menu, så du selv bestemmer, hvornår der bruges opslag.

Apps Script: batch

function opdaterListe() {
  const ark = SpreadsheetApp.getActiveSheet();
  const antal = ark.getLastRow() - 1;
  if (antal < 1) return;

  const cvrer = ark.getRange(2, 1, antal, 1).getValues()
    .map(function (r) { return String(r[0]).replace(/\D/g, ""); });
  const gyldige = cvrer.filter(function (c, i) { return c.length === 8 && cvrer.indexOf(c) === i; });

  const fundet = {};
  for (let i = 0; i < gyldige.length; i += 100) {
    const res = UrlFetchApp.fetch(BASE + "/company/batch", {
      method: "post",
      contentType: "application/json",
      headers: { Authorization: "Bearer " + noegle_() },
      payload: JSON.stringify({ cvr_numbers: gyldige.slice(i, i + 100) }),
      muteHttpExceptions: true,
    });
    if (res.getResponseCode() !== 200) {
      throw new Error("FirmaAPI svarede " + res.getResponseCode() + ": " + res.getContentText());
    }
    JSON.parse(res.getContentText()).results.forEach(function (f) { fundet[f.cvr] = f; });
  }

  const raekker = cvrer.map(function (c) {
    const f = fundet[c];
    return f ? [f.name, f.status, f.city, f.employees] : ["Ikke fundet", "", "", ""];
  });
  ark.getRange(2, 2, raekker.length, 4).setValues(raekker);
}

Batch kræver Basis. På den gratis plan kan du bruge funktionen ovenfor i stedet.

Gode vaner, så du ikke løber tør for opslag

  • Test først med en cvr_test_-nøgle. Den giver syntetiske data og bruger ikke af kvoten.
  • Fjern mellemrum og bindestreger fra CVR-numre. API'et forventer otte cifre, ellers svarer det 400 INVALID_CVR.
  • Gem resultatet som værdier, når du er tilfreds, så arket ikke slår de samme virksomheder op igen.
  • Del aldrig en fil eller et ark, hvor din live-nøgle står i klartekst.
  • Gratis-planen giver 1.000 opslag om måneden. Se priser, hvis du har brug for mere.

Ofte stillede spørgsmål

Kan jeg slå CVR-numre op med en almindelig Excel-formel?

Ikke med de indbyggede funktioner. WEBSERVICE i Excel og IMPORTDATA i Google Sheets kan ikke sende en Authorization-header, som API'et kræver. Brug Power Query i Excel eller Apps Script i Google Sheets, som begge kan sætte headeren.

Skal jeg kunne programmere?

Du skal kunne kopiere et stykke kode ind i Power Query eller Apps Script og skifte din API-nøgle ind. Du behøver ikke at kunne programmere for at bruge eksemplerne, men du bør forstå, hvor din nøgle står, og hvem der kan se den.

Hvor mange opslag bruger jeg, når jeg opdaterer arket?

Hver række, der bliver slået op, tæller som ét opslag. En forespørgsel på 500 CVR-numre bruger altså cirka 500 opslag, hver gang den opdateres. Den gratis plan har 1.000 opslag om måneden, så sæt opdatering til manuel, og brug batch-opslag, hvis du har Basis.

Er det sikkert at have API-nøglen i en Excel-fil?

Det er den kun, så længe filen ikke deles. Nøglen står som tekst i forespørgslen og kan læses af alle med adgang til filen. Del derfor ikke projektmappen, eller opret en nøgle, du kan slette igen. I Google Sheets gemmer du nøglen i Script Properties og ikke i et ark.

Kan jeg hente regnskaber og ejere ind i Excel?

Ja, hvis din plan giver adgang. Tilføj include=finance eller include=history til opslaget og udvid de felter, du vil bruge. Regnskaber kræver Standard og ejerregister kræver Basis.

Prøv det med din egen API-nøgle

Opret en gratis konto, og hent din API-nøgle med det samme. Intet kreditkort kræves.