LocationCode.meLocationCode.me

LocationCode Specification v1

Everything you need to integrate the LocationCode 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:

STQ.6S5.X3R

The dots are display separators only and carry no geographic information. The canonical database/API representation is STQ6S5X3R. 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.

How it works

A LocationCode is not an address and not a database entry — it is a purely mathematical conversion of a WGS84 latitude and longitude into a fixed set of characters. The same coordinates always produce the same code, anywhere in the world, with no lookup tables and no external service.

Each code identifies a small geographic cell rather than an exact point. The decode endpoint returns the cell's bounds and centre, together with its width, height and area in metres calculated at the decoded latitude. The centre is for display only — it is not the original GPS coordinate.

Input latitude must be between −90 and 90 and longitude between −180 and 180. Invalid or non-finite values are rejected.

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.

Precise variant (v2) — 12 characters

The default LocationCode is always the nine-character value. For work that needs centimetre-level detail there is an optional precise variant: eleven characters identifying the location plus one final check character, displayed as XXX.XXX.XXX.XXX.

A precise code is produced by the same mathematical process as a standard code, simply carried to greater precision, so a precise code always starts with the standard code for the same point: HDK.SZF.VN3 → HDK.SZF.VN3.BH8. A precise cell is about 0.15 m tall and 0.09–0.15 m wide, roughly 0.01–0.02 m².

The 12th character is a check character. It carries no location information and is never used to derive a position; it exists so a mistyped precise code is rejected rather than resolving to the wrong place. Every single-character typo is detected.

The API accepts both lengths: 9 characters (standard, version 1) and 12 characters (precise, version 2). Nine-character codes never carry a check character.

LocationCode 3D — 16 characters, displayed XXXX.XXXX.XXXX.XXXX

LocationCode 3D adds a vertical dimension. It is a separate format from Standard and Precise — it does not change them, and it is always displayed in four groups of four so it is instantly recognisable: XXXX.XXXX.XXXX.XXXX. Fifteen characters carry the data (26 bits longitude, 25 bits latitude, 15 bits height and 9 bits reserved for future versions) and the sixteenth is a check character that carries no position. Resolution is about 0.6 m horizontally and 0.5 m vertically, over a vertical range of −1,000 m to +15,000 m above sea level.

A 3D code is only generated when a real height is available. Heights are never assumed or substituted with zero. Vertical position comes from the device and is usually far less accurate than horizontal position, so a 3D code describes a height band, not a proven floor or storey.

Status: live and stable. 3D is available now on the website and the API — anyone can generate, share and decode 3D codes. The layout and check character will never change, so a 3D code created today keeps working for good, and permanent test vectors guard that promise. Every 3D code is 16 characters. Standard and Precise are unchanged.

GET /api/v1/encode?lat=51.4816&lng=-3.1791&alt=74&precision=3d

{
  "type": "3d",
  "code": "HDKSZFVN3B34DJ2Z",
  "formattedCode": "HDKS.ZFVN.3B34.DJ2Z",
  "version": 1,
  "vertical": { "minMetres": 73.73, "maxMetres": 74.219, "centreMetres": 73.975, "reference": "wgs84-ellipsoidal-metres" },
  "checksumValid": true
}

Omitting alt with precision=3d returns missing_parameter. Decoding a 3D code returns horizontal bounds plus the vertical range, and a 3D code with a failed check character is rejected with invalid_code.

Authentication and rate limits

Every API request needs a key. Create a free account and key on the developer dashboard, then send the key as a header (a Bearer token or ?key= query parameter also works):

X-API-Key: lc_live_xxxxxxxxxxxxxxxxxxxxx

The Free plan allows 500 requests per day per account. Paid plans raise that allowance. Responses include X-RateLimit-Limit and X-RateLimit-Remaining. A missing, unrecognised or revoked key returns 401; exceeding your daily allowance returns 429.

The website and app remain free for anyone to use without an account — keys apply to the API only.

GET /api/v1/lookup — one endpoint for all three formats

A single endpoint handles everything. Send q as coordinates to get codes back, or as a LocationCode to get a position back. The format is worked out for you: coordinates are recognised by their decimal numbers, and codes by their length and grouping (9 characters standard, 12 precise, 16 3D). Upper or lower case, with or without dots.

GET /api/v1/lookup?q=51.4816,-3.1791

{
  "mode": "encode",
  "standard": { "code": "HDKSZFVN3", "formattedCode": "HDK.SZF.VN3", "version": 1, "variant": "standard" },
  "precise": { "code": "HDKSZFVN3BH8", "formattedCode": "HDK.SZF.VN3.BH8", "version": 2, "variant": "precise" },
  "threeD": null
}

GET /api/v1/lookup?q=51.4816,-3.1791,74      (third value is the height in metres)

{
  "mode": "encode",
  "standard": { ... },
  "precise": { ... },
  "threeD": { "code": "HDKSZFVN3B34DJ2Z", "formattedCode": "HDKS.ZFVN.3B34.DJ2Z", "version": 2, "variant": "3d", "vertical": { ... } }
}

GET /api/v1/lookup?q=HDKS.ZFVN.3B34.DJ2Z

{
  "mode": "decode",
  "type": "3d",
  "centre": { ... },
  "bounds": { ... },
  "vertical": { ... },
  "cell": { ... }
}

lat, lng and alt may be sent as separate parameters instead of q. Without a height, no 3D code is returned — heights are never assumed. An unrecognisable code returns invalid_code. The separate /encode and /decode endpoints below continue to work unchanged.

GET /api/v1/encode

Add precision=precise for the 12-character variant. The parameter is optional and defaults to standard.

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

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

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

{
  "code": "HDKSZFVN3BH8",
  "formattedCode": "HDK.SZF.VN3.BH8",
  "version": 2,
  "variant": "precise"
}

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

{
  "code": "HDKSZFVN3",
  "formattedCode": "HDK.SZF.VN3",
  "version": 1,
  "variant": "standard",
  "standard": { "code": "HDKSZFVN3", "formattedCode": "HDK.SZF.VN3", "version": 1, "variant": "standard" },
  "precise": { "code": "HDKSZFVN3BH8", "formattedCode": "HDK.SZF.VN3.BH8", "version": 2, "variant": "precise" }
}

GET /api/v1/decode/{code}

Accepts formatted or unformatted, upper or lower case, 9 or 12 characters. A precise code whose check character does not match is rejected with invalid_code.

GET /api/v1/decode/HDKSZFVN3

{
  "code": "HDKSZFVN3",
  "formattedCode": "HDK.SZF.VN3",
  "version": 1,
  "variant": "standard",
  "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

Official test vectors (permanent)

These are the official reference codes. 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.