LocationCode Specification v1

This document is complete enough to implement LocationCode v1 independently, without using our API. The specification is permanent: existing codes remain valid forever.

What a LocationCode is

A LocationCode is a 9-character code identifying a small geographic cell anywhere on Earth. It is displayed as three groups of three characters:

6HY.H8V.1FH

The dots are display separators only and carry no geographic information. The canonical database/API representation is 6HYH8V1FH. A code identifies a cell, not an exact point, and is computed purely from WGS84 latitude and longitude — no lookup table, no external service, no randomness.

Alphabet

The alphabet is exactly 32 characters, so each character carries 5 bits:

23456789ABCDEFGHJKLMNPQRSTUVWXYZ

Confusable characters (0, 1, I, O) are excluded. Nine characters × 5 bits = 45 bits. 32⁹ = 35,184,372,088,832 possible codes.

Input normalisation

Latitude must satisfy −90 ≤ lat ≤ 90. Longitude must satisfy −180 ≤ lng < 180; longitude +180 is normalised to −180. Invalid or non-finite values are rejected.

Encoding algorithm

Geohash-style binary subdivision. Start with latitude range [−90, +90] and longitude range [−180, +180]. Generate exactly 45 bits, starting with longitude and alternating longitude, latitude, longitude, latitude…

For each bit, take the midpoint of the current range for that dimension. If the coordinate ≥ midpoint, emit 1 and keep the upper half; otherwise emit 0 and keep the lower half. This yields 23 longitude bits and 22 latitude bits. Split the 45 bits into nine 5-bit groups, read each as an integer 0–31, and map it to the alphabet index.

bits  = 10110 01101 11100 ... (45 bits, longitude first)
chars = ALPHABET[22] ALPHABET[13] ALPHABET[28] ...

Decoding algorithm

Normalise the input: remove dots, spaces and hyphens, then uppercase. Validate that exactly nine characters remain and that every character exists in the alphabet. Convert each character to its 5-bit value to reconstruct the 45-bit path, then replay the subdivision from [−90, +90] / [−180, +180] in the same longitude-first order.

The resulting ranges are the cell bounds. Return south/north latitude, west/east longitude, the centre point, and the cell width, height and area in metres calculated at the decoded latitude. The centre is for display only — it is not the original GPS coordinate.

Resolution and accuracy

Cell height is constant (about 4.8 m). Cell width varies with latitude because longitude distances shrink towards the poles — roughly 3.0 m in the UK, 4.8 m at the equator. Always calculate the area from the decoded bounds rather than hard-coding a figure.

Grid resolution and device GPS accuracy are different things. Report position.coords.accuracy separately.

Optional future check character

The official LocationCode is always the nine-character value. A tenth check character (6HY.H8V.1FH-K) may be defined later; it will carry no location information and will never be required for decoding. Write parsers so an optional tenth character can be accepted without breaking the nine-character format.

Authentication and rate limits

The API is currently free and requires no key. Fair use is 1,000 requests per day per client. API keys of the form lc_live_… will be introduced for higher volumes; when they are, pass them as a header:

X-API-Key: lc_live_xxxxxxxxxxxxxxxxxxxxx

Adding a key will not change the request or response shape, so integrations built today keep working.

GET /api/v1/encode

GET /api/v1/encode?lat=51.4816&lng=-3.1791

{
  "code": "HDKSZFVN3",
  "formattedCode": "HDK.SZF.VN3",
  "version": 1
}

GET /api/v1/decode/{code}

Accepts formatted or unformatted, upper or lower case.

GET /api/v1/decode/HDKSZFVN3

{
  "code": "HDKSZFVN3",
  "formattedCode": "HDK.SZF.VN3",
  "version": 1,
  "centre": { "latitude": 51.48161172866821, "longitude": -3.1791043281555176 },
  "bounds": {
    "south": 51.481590270996094,
    "west": -3.1791257858276367,
    "north": 51.48163318634033,
    "east": -3.1790828704833984
  },
  "cell": {
    "widthMetres": 2.9751478062254693,
    "heightMetres": 4.777314267823515,
    "areaSquareMetres": 14.213216063564765
  }
}

Errors

400 { "error": "missing_parameter",    "message": "Both lat and lng are required." }
400 { "error": "invalid_coordinates", "message": "Latitude must be between -90 and 90" }
400 { "error": "invalid_code",        "message": "Invalid LocationCode character: 0" }
429 { "error": "rate_limited",        "message": "Daily request allowance exceeded." }

Examples

curl

curl "https://locationcode.me/api/v1/encode?lat=51.4816&lng=-3.1791"
curl "https://locationcode.me/api/v1/decode/HDK.SZF.VN3"

JavaScript

const res = await fetch(
  "https://locationcode.me/api/v1/encode?lat=51.4816&lng=-3.1791"
);
const { formattedCode } = await res.json();
console.log(formattedCode); // HDK.SZF.VN3

TypeScript

interface EncodeResponse {
  code: string;
  formattedCode: string;
  version: number;
}

export async function encode(lat: number, lng: number): Promise<EncodeResponse> {
  const res = await fetch(
    `https://locationcode.me/api/v1/encode?lat=${lat}&lng=${lng}`
  );
  if (!res.ok) throw new Error(`LocationCode error ${res.status}`);
  return (await res.json()) as EncodeResponse;
}

C#

using System.Net.Http.Json;

var http = new HttpClient();
var result = await http.GetFromJsonAsync<EncodeResponse>(
    "https://locationcode.me/api/v1/encode?lat=51.4816&lng=-3.1791");

Console.WriteLine(result!.FormattedCode);

public record EncodeResponse(string Code, string FormattedCode, int Version);

Python

import requests

r = requests.get(
    "https://locationcode.me/api/v1/encode",
    params={"lat": 51.4816, "lng": -3.1791},
    timeout=10,
)
r.raise_for_status()
print(r.json()["formattedCode"])  # HDK.SZF.VN3

Reference implementation (encode)

const ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";

function encode(lat, lng) {
  if (lng === 180) lng = -180;
  let latMin = -90, latMax = 90, lonMin = -180, lonMax = 180;
  let code = "", value = 0, bits = 0, isLon = true;

  for (let i = 0; i < 45; i++) {
    let bit;
    if (isLon) {
      const mid = (lonMin + lonMax) / 2;
      if (lng >= mid) { bit = 1; lonMin = mid; } else { bit = 0; lonMax = mid; }
    } else {
      const mid = (latMin + latMax) / 2;
      if (lat >= mid) { bit = 1; latMin = mid; } else { bit = 0; latMax = mid; }
    }
    isLon = !isLon;
    value = value * 2 + bit;
    if (++bits === 5) { code += ALPHABET[value]; value = 0; bits = 0; }
  }
  return code;
}

Official test vectors (permanent)

Any conforming implementation must reproduce these exactly. Machine-readable version: /api/v1/test-vectors.

PlaceLatitudeLongitudeLocationCode
Cardiff, Wales51.4816-3.1791HDK.SZF.VN3
London, England51.5074-0.1278HDP.VK2.EUQ
New York, USA40.7128-74.006ER7.RFH.W5P
Tokyo, Japan35.6762139.6503XN9.8DY.EJZ
Sydney, Australia-33.8688151.2093R5H.X4G.99C
Equator / Prime meridian00S22.222.222
International date line0-179.999999A22.222.222
Northern extreme900UPC.PCP.CPC
Southern extreme-900J22.222.222

Privacy

Coordinates sent to the API are used to compute a response and are not permanently retained. Requests are counted for rate limiting only. Anyone you share a LocationCode with can determine the location it represents.