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:
23456789ABCDEFGHJKLMNPQRSTUVWXYZConfusable 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_xxxxxxxxxxxxxxxxxxxxxAdding 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.VN3TypeScript
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.VN3Reference 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.
| Place | Latitude | Longitude | LocationCode |
|---|---|---|---|
| Cardiff, Wales | 51.4816 | -3.1791 | HDK.SZF.VN3 |
| London, England | 51.5074 | -0.1278 | HDP.VK2.EUQ |
| New York, USA | 40.7128 | -74.006 | ER7.RFH.W5P |
| Tokyo, Japan | 35.6762 | 139.6503 | XN9.8DY.EJZ |
| Sydney, Australia | -33.8688 | 151.2093 | R5H.X4G.99C |
| Equator / Prime meridian | 0 | 0 | S22.222.222 |
| International date line | 0 | -179.999999 | A22.222.222 |
| Northern extreme | 90 | 0 | UPC.PCP.CPC |
| Southern extreme | -90 | 0 | J22.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.
