Æ, ø and å: character sets and URL encoding

Send æøå correctly URL-encoded, read UTF-8 responses without broken characters, open CSV in Excel with the right letters, and understand the oe/aa/ae folding in autocomplete.

4 min Level: Beginner ✓ Code tested

Danish addresses are full of æ, ø and å. The API uses UTF-8 all the way: in the URL, in JSON and in CSV. Problems appear when one link in the chain uses another character set, usually Windows-1252 (also called Latin-1 or ANSI). The result is either empty responses or text like RÃ¥dhuspladsen.

Recognise the problem

You seeIt should beCause
æ, ø, Ã¥æ, ø, åUTF-8 read as Windows-1252
Æ, Ø, Ã…Æ, Ø, ÅThe same with capitals
� or ?æ, ø or åWindows-1252 read as UTF-8, or the character lost in a conversion
R%E5dhuspladsen in the URLR%C3%A5dhuspladsenThe text was URL-encoded as Latin-1
å in JSONåNot a problem. The JSON parser turns it into å

Send æøå in the URL

In a URL, æ, ø and å must be sent as UTF-8 bytes, percent-encoded:

CharacterURL-encoded
æ / Æ%C3%A6 / %C3%86
ø / Ø%C3%B8 / %C3%98
å / Å%C3%A5 / %C3%85
space%20 (or + in the query string)

Never build the URL by concatenating text. Let your library encode the parameters. With curl, -G and --data-urlencode do it for you:

Shell#curl-urlencode
curl -s -G "https://api.danadresse.dk/autocomplete" \
  --data-urlencode "q=Rådhuspladsen 1" \
  --data-urlencode "per_side=3" \
  -H "X-Api-Key: $DANADRESSE_API_KEY"

In Python, requests encodes everything you pass in params:

Python#python-params
import os
import requests

r = requests.get(
    "https://api.danadresse.dk/autocomplete",
    params={"q": "Rådhuspladsen 1", "per_side": 1},
    headers={"X-Api-Key": os.environ["DANADRESSE_API_KEY"]},
    timeout=10,
)
r.raise_for_status()
print(r.url)                              # ...?q=R%C3%A5dhuspladsen+1&per_side=1
print(r.json()[0]["forslagstekst"])       # r.json() always reads UTF-8

In PHP, use http_build_query, which encodes UTF-8 strings correctly. Do not use utf8_decode or mb_convert_encoding to Latin-1 before the call:

PHP#php-build-query
<?php
$query = http_build_query(["q" => "Rådhuspladsen 1", "per_side" => 3]);
$ctx = stream_context_create(["http" => [
    "header" => "X-Api-Key: " . getenv("DANADRESSE_API_KEY"),
    "timeout" => 10,
]]);
$body = file_get_contents("https://api.danadresse.dk/autocomplete?$query", false, $ctx);
foreach (json_decode($body, true) as $suggestion) {
    echo $suggestion["forslagstekst"], "\n";
}

In JavaScript, use URL and searchParams.set(), as in Your first API call.

If your text comes from an old system, a file or a database in Windows-1252, convert it to UTF-8 before you send it. In Python: text = raw_bytes.decode("cp1252"). On the command line: iconv -f WINDOWS-1252 -t UTF-8 in.csv > out.csv.

Read the response as UTF-8

JSON responses have Content-Type: application/json without a charset parameter. JSON is always UTF-8, and most clients know that: r.json() in Python, res.json() in JavaScript and json_decode() in PHP give the right characters.

Problems come when a client guesses the character set from the header:

  • Windows PowerShell 5.1 (Invoke-RestMethod, Invoke-WebRequest) reads a response without charset as Latin-1. Use PowerShell 7, or read the bytes and decode them yourself:
PowerShell#powershell-utf8
$headers = @{ "X-Api-Key" = $env:DANADRESSE_API_KEY }
$res = Invoke-WebRequest -Uri "https://api.danadresse.dk/postnumre/2900" -Headers $headers -UseBasicParsing
$json = [System.Text.Encoding]::UTF8.GetString($res.RawContentStream.ToArray()) | ConvertFrom-Json
$json.navn
  • Older HTTP libraries in, for example, Java, .NET or VBA use the system's default character set when turning bytes into text. Specify UTF-8 explicitly.
  • The database must be able to store the characters too. In MySQL and MariaDB the column and the connection must be utf8mb4.

CSV in Excel

format=csv gives UTF-8 without a BOM (byte order mark). If you double-click the file in Excel on Windows, Excel guesses Windows-1252 and shows RÃ¥dhuspladsen. There are three fixes:

  1. Import instead of opening. In Excel: Data → From Text/CSV, choose the file, and set File Origin to 65001: Unicode (UTF-8).
  2. Power Query. Set the character set directly:
Power Query (M)#power-query-csv
let
    Kilde = Web.Contents(
        "https://api.danadresse.dk/adresser",
        [Query = [postnr = "4200", per_side = "100", format = "csv"],
         Headers = [#"X-Api-Key" = "YOUR_API_KEY"]]),
    Csv = Csv.Document(Kilde, [Delimiter = ",", Encoding = 65001, QuoteStyle = QuoteStyle.Csv]),
    Tabel = Table.PromoteHeaders(Csv, [PromoteAllScalars = true])
in
    Tabel
  1. Save the file with a BOM. If you fetch the CSV with a script, write it with a BOM in front. Excel then opens it correctly on double-click:
Python#csv-with-bom
import os

import requests

r = requests.get(
    "https://api.danadresse.dk/adresser",
    params={"postnr": "4200", "per_side": 1000, "format": "csv"},
    headers={"X-Api-Key": os.environ["DANADRESSE_API_KEY"]},
    timeout=30,
)
r.raise_for_status()
with open("adresser.csv", "w", encoding="utf-8-sig", newline="") as f:  # utf-8-sig = UTF-8 with BOM
    f.write(r.content.decode("utf-8"))
print("Saved adresser.csv")

Excel with Danish regional settings expects a semicolon as separator. The API's CSV uses commas, so pick comma in the import dialog, or use Power Query as above. See also Excel and Power Query.

oe, aa and ae in autocomplete

Users often type without Danish characters: raadhuspladsen, kobenhavn, Soendergade. /autocomplete therefore tries variants of what the user typed:

  • aa → å, oe → ø, ae → æ (also with a capital first letter)
  • an o at the start of a word → ø, so oster finds Øster
  • on top of that a typo tolerance that can fix one or two wrong letters in longer words
Python#folding-demo
import os
import requests

r = requests.get(
    "https://api.danadresse.dk/autocomplete",
    params={"q": "raadhuspladsen 1", "per_side": 3},
    headers={"X-Api-Key": os.environ["DANADRESSE_API_KEY"]},
    timeout=10,
)
r.raise_for_status()
for suggestion in r.json():
    print(suggestion["forslagstekst"])

The folding lives in /autocomplete. The filters on /adresser and /adgangsadresser (vejnavn=, husnr=, postnr=) do not do the same folding. Write the names with æ, ø and å. If your data lacks æøå, run it through /autocomplete or /datavask/adresser first. The datavask parser reads spellings such as Aabenraa, Åbenrå and Kobenhavn and matches them to the official name. See No results?

Next steps

Related guides

Markdown source Spotted an error? Tell us API-reference (Swagger)

Try it with a free key

2,000 calls/month on the free plan. No credit card.

Create account →